home *** CD-ROM | disk | FTP | other *** search
/ PC Professionell 2006 May / PCpro_2006_05.ISO / files / mobile / fma-2.0-stable-setup.exe / {app} / source / uSyncPhonebook.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  2005-01-26  |  86.0 KB  |  2,645 lines

  1. unit uSyncPhonebook;
  2.  
  3. {
  4. *******************************************************************************
  5. * Descriptions: Synchronize's Phonebook Interface
  6. * $Source: /cvsroot/fma/fma/uSyncPhonebook.pas,v $
  7. * $Locker:  $
  8. *
  9. * Todo:
  10. *
  11. * Change Log:
  12. * $Log: uSyncPhonebook.pas,v $
  13. * Revision 1.40.2.3  2005/01/25 16:03:20  z_stoichev
  14. * Merged with 2.1 Beta 1 bugfixes
  15. *
  16. * Revision 1.40.2.2  2004/09/10 07:31:42  z_stoichev
  17. * Fixed a typo in Outlook Details
  18. * Added Outlook Details default number
  19. *
  20. * Revision 1.40.2.1  2004/09/08 19:24:25  lordlarry
  21. * New Line Char in Contact Name is read properly from the Contacts.SYNC.dat file too
  22. *
  23. * Revision 1.40  2004/08/27 20:42:00  lordlarry
  24. * Merge from newxml
  25. *
  26. * Revision 1.39.4.1  2004/08/25 15:40:20  merijnb
  27. * implemented xml parser
  28. *
  29. * Revision 1.39  2004/07/14 09:43:07  z_stoichev
  30. * - Fixed Add to Group disabled for new/del contacts.
  31. *
  32. * Revision 1.38  2004/07/06 13:51:04  z_stoichev
  33. * - Fixed Keep FMA settings on full PB refresh.
  34. * - Fixed Contact doubled if changed in phone.
  35. *
  36. * Revision 1.37  2004/07/01 15:02:17  z_stoichev
  37. * Fixed Phonebook View First-Last name order.
  38. *
  39. * Revision 1.36  2004/07/01 14:39:21  z_stoichev
  40. * Remember columns sort and order.
  41. * vCard notes support.
  42. *
  43. * Revision 1.35  2004/06/30 16:13:14  z_stoichev
  44. * Fixed Add to Group canceled but shows Adding.
  45. *
  46. * Revision 1.34  2004/06/29 10:48:38  z_stoichev
  47. * Added contact call notes support
  48. *
  49. * Revision 1.33  2004/06/24 14:56:03  z_stoichev
  50. * - Fixed Upload Contacts phone type to SIM issues.
  51. * - Fixed Max field length for ME and SM Phonebooks.
  52. * - Fixed Contact name lookup routines use ME phonebook.
  53. * - Changed Connect after unknown number call is entered.
  54. * - Changed Explorer Popup menu reorganized.
  55. * - Added Chat to Contact command to various popup menus.
  56. * - Added Add to Phonebook to various popup menus.
  57. * - Added hit Enter in SIM editor to edit contact.
  58. * - Added Remove favorite item confirmation message.
  59. * - Added Download entire SIM phonebook.
  60. * - Added New SIM editor columns (phone type, status).
  61. * - Broken Phonebook editors Sorting is not remembered.
  62. *
  63. * Revision 1.32  2004/06/24 09:00:00  z_stoichev
  64. * Find contact by number
  65. *
  66. * Revision 1.31  2004/06/23 13:48:20  z_stoichev
  67. * Added Chat support
  68. *
  69. * Revision 1.30  2004/06/22 14:32:09  z_stoichev
  70. * - Fixed Export file type filter misusage/order.
  71. * - Fixed Export contacts multiple headers (csv).
  72. * - Added Export contacts DisplayName field (csv).
  73. * - Added Import contacts status line feedback.
  74. * - Added Copy from Phonebook to SIM w/phone type.
  75. *
  76. * Revision 1.29  2004/06/19 11:15:22  z_stoichev
  77. * - Added Download entire phonebook.
  78. * - Changed Contact Group icons.
  79. *
  80. * Revision 1.28  2004/06/18 13:48:49  z_stoichev
  81. * - Fixed Do not lose personalization on sync.
  82. * - Added Contact DisplayName auto-update on Sync.
  83. * - Added Track edited contact in the list.
  84. *
  85. * Revision 1.27  2004/05/21 14:39:48  z_stoichev
  86. * Fixed Contact name changes not saved
  87. * Added Contact Display name support
  88. *
  89. * Revision 1.26  2004/05/21 10:09:05  z_stoichev
  90. * Changed logging handle routines.
  91. *
  92. * Revision 1.25  2004/05/19 18:34:16  z_stoichev
  93. * Build 0.1.0.35c
  94. *
  95. * Revision 1.24  2004/04/01 15:04:54  z_stoichev
  96. * New contact flag support
  97. *
  98. * Revision 1.23  2004/03/26 18:37:40  z_stoichev
  99. * Build 0.1.0.35 RC5
  100. *
  101. * Revision 1.22  2004/03/12 14:42:53  z_stoichev
  102. * Using new vCard code.
  103. * Download entire phonebook progress status.
  104. *
  105. * Revision 1.21  2004/03/09 15:02:28  z_stoichev
  106. * Use default contact GUID generator.
  107. * Offer to exchange home/cell on new phonebook.
  108. *
  109. * Revision 1.20  2004/03/07 21:57:43  z_stoichev
  110. * Synchroize re-coded from scratch.
  111. *
  112. * Revision 1.19  2004/01/28 17:39:53  z_stoichev
  113. * Popup menu rearranged.
  114. *
  115. * Revision 1.18  2004/01/13 12:29:04  z_stoichev
  116. * Fixed Phonebook not saved when empty.
  117. * Fixed Phonebook not sync Explorer view on Delete.
  118. * Fixed Synchronization Log events.
  119. * Fixed Export Contact to vCard, added missing LUID.
  120. * Changed Phonebook and Messages popup menu.
  121. * Added Import Contacts (vCard only).
  122. *
  123. * Revision 1.17  2003/12/16 17:40:07  z_stoichev
  124. * Columnt renamed to First and Last Name.
  125. * Fixed saving personalization data with spaces.
  126. *
  127. * Revision 1.16  2003/12/12 16:54:24  z_stoichev
  128. * Added view customization support.
  129. *
  130. * Revision 1.15  2003/12/02 16:35:52  z_stoichev
  131. * Memory leaks and other bugfixes for vCard.
  132. *
  133. * Revision 1.14  2003/12/01 16:03:12  z_stoichev
  134. * Support for Own card editing.
  135. *
  136. * Revision 1.13  2003/12/01 12:02:26  z_stoichev
  137. * Hit Enter shows properties window.
  138. *
  139. * Revision 1.12  2003/11/28 09:38:07  z_stoichev
  140. * Merged with branch-release-1-1 (Fma 0.10.28c)
  141. *
  142. * Revision 1.11.2.20  2003/11/27 12:55:04  z_stoichev
  143. * Sort list after Sync.
  144. *
  145. * Revision 1.11.2.19  2003/11/26 12:26:16  z_stoichev
  146. * Export to CSV fixed and default number export added.
  147. * Added Outlook 2003 support.
  148. *
  149. * Revision 1.11.2.18  2003/11/21 13:58:22  z_stoichev
  150. * Support for creating new contact with default cell number.
  151. *
  152. * Revision 1.11.2.17  2003/11/21 10:58:30  z_stoichev
  153. * Made SyncLog public method.
  154. *
  155. * Revision 1.11.2.16  2003/11/19 14:05:52  z_stoichev
  156. * Fixed vcf export of multiple items.
  157. *
  158. * Revision 1.11.2.15  2003/11/19 12:47:37  z_stoichev
  159. * Clear phonebook on Database load.
  160. *
  161. * Revision 1.11.2.14  2003/11/14 15:41:03  z_stoichev
  162. * Updates for patch 27d.
  163. *
  164. * Revision 1.11.2.13  2003/11/13 16:37:10  z_stoichev
  165. * Changed images.
  166. *
  167. * Revision 1.11.2.12  2003/11/12 16:21:08  z_stoichev
  168. * Allow contact properties from Explorer popup menu.
  169. *
  170. * Revision 1.11.2.11  2003/11/12 08:02:29  z_stoichev
  171. * Save data on refresh.
  172. *
  173. * Revision 1.11.2.10  2003/11/11 13:24:31  z_stoichev
  174. * Add personalization support.
  175. *
  176. * Revision 1.11.2.9  2003/11/10 14:03:11  z_stoichev
  177. * RC3
  178. *
  179. * Revision 1.11.2.8  2003/11/07 13:55:08  z_stoichev
  180. * Fixed delete new items.
  181. *
  182. * Revision 1.11.2.7  2003/11/07 11:15:23  z_stoichev
  183. * Add to group wizard added.
  184. * Right-click select.
  185. *
  186. * Revision 1.11.2.6  2003/11/06 16:13:37  z_stoichev
  187. * Popup menu changed.
  188. * Delete sanity chechs.
  189. * Can delete multiple items.
  190. *
  191. * Revision 1.11.2.5  2003/10/30 13:24:32  z_stoichev
  192. * Contact fileds restrictions saved to a file.
  193. * Fixed timeout error when modifying contact
  194. * in offline mode.
  195. *
  196. * Revision 1.11.2.4  2003/10/29 12:00:48  z_stoichev
  197. * Update explorer on contact change.
  198. *
  199. * Revision 1.11.2.3  2003/10/28 13:01:59  z_stoichev
  200. * Added default contact number usage.
  201. * Contact will not be marked as modified
  202. * if only custom Fma contact data is modified.
  203. *
  204. * Revision 1.11.2.2  2003/10/27 15:19:34  z_stoichev
  205. * Fixed: Contact was updated even if there aro no changes made.
  206. *
  207. * Revision 1.11.2.1  2003/10/27 07:22:54  z_stoichev
  208. * Build 0.1.0 RC1 Initial Checkin.
  209. *
  210. * Revision 1.11  2003/10/24 12:38:35  z_stoichev
  211. * Ask when deleting contact.
  212. * Hide unused splitter.
  213. * Fixed: list sort was not working.
  214. * Title length reduced.
  215. *
  216. * Revision 1.10  2003/10/23 11:51:28  z_stoichev
  217. * Edit/create contact uses external unit uEditContact.
  218. * Popup menu recreated.
  219. * Max fields length fixed.
  220. * Font changed.
  221. *
  222. * Revision 1.9  2003/10/22 13:07:38  z_stoichev
  223. * Make progress dialog optional.
  224. *
  225. * Revision 1.8  2003/10/16 11:15:40  z_stoichev
  226. * Use ConnProgress instead of SyncProgress, so
  227. * we can remove uSyncProgress.pas from project.
  228. * Show more detailed progress information.
  229. *
  230. * Revision 1.7  2003/10/14 07:25:04  z_stoichev
  231. * Show only one Sync Log window and
  232. * update log in realtime.
  233. *
  234. * Revision 1.6  2003/10/13 15:18:30  z_stoichev
  235. * Fixed Sync when contact is deleted on phone
  236. * and modified on PC. before it was failing with
  237. * "not found" error message.
  238. *
  239. * Revision 1.5  2003/10/13 14:16:22  z_stoichev
  240. * Modified to reflect changes in Obex methods.
  241. *
  242. * Revision 1.4  2003/10/10 13:18:48  z_stoichev
  243. * Sync speed up, refresh only on changes.
  244. * Progress diaglog is displayed.
  245. * No popup after sync.
  246. * Default message "No items to display..."
  247. *
  248. * Revision 1.3  2003/08/31 07:14:06  bufflig
  249. * Added vCard export of contacts.
  250. * Added handling of Name and Surname fields in contact edit so
  251. * that they are limited to 30 characters together instead of 15 each.
  252. * Made vCard be reused throughout the form, created once in
  253. * constructor and destroyed in destructor.
  254. *
  255. * Revision 1.2  2003/07/02 12:35:48  crino77
  256. * Automatic refresh after sync
  257. * popup at the end of sync
  258. * removed keypress on number fields
  259. * added handle exception on sync aborted
  260. * added export contact's list
  261. * added force update
  262. * added force new contact to restore phonebook
  263. * added unicode support in textbox
  264. *
  265. * Revision 1.1  2003/02/14 14:25:33  crino77
  266. * Initial Checkin
  267. *
  268. *
  269. *
  270. *******************************************************************************
  271. }
  272.  
  273. interface
  274.  
  275. uses
  276.   Windows, Messages, SysUtils, StrUtils,Classes, Graphics, Controls, Forms, Dialogs,
  277.   Menus, ImgList, VirtualTrees, ExtCtrls, StdCtrls, TntStdCtrls, TntClasses, ActnList,
  278.   uVcard, uPromptConflict, uSyncLog, Mask, Placemnt;
  279.  
  280. type
  281.   TNumberPos = Record
  282.     home, work, cell, fax, other : integer;
  283.   end;
  284.   TContactData = Record
  285.     title, name, surname, displayname, org, email: WideString;
  286.     home, work, cell, fax, other : WideString;
  287.     CDID: TGUID;
  288.     LUID : WideString;
  289.     StateIndex : Integer; //0 new entry;1 modified entry;2 deleted entry;3 normal entry
  290.     DefaultIndex: Integer; //0 none;1 cell;2 work;3 home;4 other
  291.     Position: TNumberPos;
  292.     picture, sound: WideString;
  293.   end;
  294.   PContactData = ^TContactData;
  295.  
  296.   TfrmSyncPhonebook = class(TFrame)
  297.     Panel1: TPanel;
  298.     ListContacts: TVirtualStringTree;
  299.     btnSYNC: TButton;
  300.     Panel2: TPanel;
  301.     btnLOG: TButton;
  302.     PopupMenu1: TPopupMenu;
  303.     ForceUpdate: TMenuItem;
  304.     Exportselectedcontacts1: TMenuItem;
  305.     N1: TMenuItem;
  306.     ForceNewContact: TMenuItem;
  307.     NoItemsPanel: TPanel;
  308.     N2: TMenuItem;
  309.     Properties1: TMenuItem;
  310.     N3: TMenuItem;
  311.     NewContact1: TMenuItem;
  312.     Delete1: TMenuItem;
  313.     N4: TMenuItem;
  314.     N5: TMenuItem;
  315.     AddtoGroup1: TMenuItem;
  316.     txtCC: TEdit;
  317.     txtLUID: TEdit;
  318.     UndoLastChange1: TMenuItem;
  319.     SendMsg1: TMenuItem;
  320.     voicecall1: TMenuItem;
  321.     FormStorage1: TFormStorage;
  322.     ImportContacts1: TMenuItem;
  323.     OpenDialog1: TOpenDialog;
  324.     ClearChangedFlag1: TMenuItem;
  325.     N6: TMenuItem;
  326.     PopupMenu2: TPopupMenu;
  327.     FirstLast1: TMenuItem;
  328.     LastFirst1: TMenuItem;
  329.     N7: TMenuItem;
  330.     DownloadEntirePhonebook1: TMenuItem;
  331.     ChatContact1: TMenuItem;
  332.     //List
  333.     procedure ListContactsGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
  334.       Column: TColumnIndex; TextType: TVSTTextType;
  335.       var CellText: WideString);
  336.     procedure ListContactsCompareNodes(Sender: TBaseVirtualTree; Node1,
  337.       Node2: PVirtualNode; Column: TColumnIndex; var Result: Integer);
  338.     procedure ListContactsHeaderClick(Sender: TVTHeader; Column: TColumnIndex;
  339.       Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
  340.     procedure ListContactsGetImageIndex(Sender: TBaseVirtualTree;
  341.       Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex;
  342.       var Ghosted: Boolean; var ImageIndex: Integer);
  343.     procedure btnEditClick(Sender: TObject);
  344.     //Button
  345.     procedure btnNEWClick(Sender: TObject);
  346.     procedure btnDELClick(Sender: TObject);
  347.     procedure btnSYNCClick(Sender: TObject);
  348.     procedure btnLOGClick(Sender: TObject);
  349.     procedure ForceUpdateClick(Sender: TObject);
  350.     procedure ForceNewContactClick(Sender: TObject);
  351.     procedure ListContactsAfterPaint(Sender: TBaseVirtualTree;
  352.       TargetCanvas: TCanvas);
  353.     procedure PopupMenu1Popup(Sender: TObject);
  354.     procedure AddToGroupClick(Sender: TObject);
  355.     procedure UndoLastChange1Click(Sender: TObject);
  356.     procedure ListContactsIncrementalSearch(Sender: TBaseVirtualTree;
  357.       Node: PVirtualNode; const SearchText: WideString;
  358.       var Result: Integer);
  359.     procedure ListContactsKeyDown(Sender: TObject; var Key: Word;
  360.       Shift: TShiftState);
  361.     procedure ImportContacts1Click(Sender: TObject);
  362.     procedure ClearChangedFlag1Click(Sender: TObject);
  363.     procedure FirstLast1Click(Sender: TObject);
  364.     procedure DownloadEntirePhonebook1Click(Sender: TObject);
  365.     procedure FormStorage1SavePlacement(Sender: TObject);
  366.     procedure FormStorage1RestorePlacement(Sender: TObject);
  367.     procedure ListContactsHeaderMouseUp(Sender: TVTHeader;
  368.       Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
  369.   private
  370.     FSyncConflict: Integer;
  371.     VCard: TVCard;
  372.     CC: WideString;
  373.     FUndoEdit: TContactData;
  374.     FUndoIndx: cardinal;
  375.     function Synchronize: boolean;
  376.     function FullRefresh: boolean;
  377.     function CheckInArray(A: array of widestring; S: Widestring): boolean;
  378. //    procedure ResetInArray(var A: array of widestring; S: Widestring);
  379.     function EraseContact(LUID :Widestring; Log :Boolean = True):Boolean;
  380.     function LFindContact(LUID :Widestring; var AContact: PContactData):Boolean;
  381.     function PromptConflict(NameContact: WideString; Info:WideString): boolean;
  382.     function GetPhoneCapacity: Integer;
  383.     procedure ForceContact(State: integer);
  384.     procedure DoFirstImportCheck;
  385.   public
  386.     State: Integer;
  387.     SelContact: PContactData;
  388.     FMaxRecME,FMaxNameLen,FMaxTitleLen,FMaxOrgLen,FMaxMailLen,FMaxTellen: cardinal;
  389.     constructor Create(AOwner: TComponent); override;
  390.     destructor Destroy; override;
  391.     procedure SyncLog(Desc: WideString);
  392.     procedure RenderListView(const sl: TStrings);
  393.     procedure RenderGUIDs;
  394.     procedure SaveContacts(FileName:WideString);
  395.     procedure LoadContacts(FileName:WideString);
  396.     procedure ExportList(FileType:Integer; Filename: WideString);
  397.     procedure OnConnected;
  398.     function IsUniqueGUID(who: PContactData): boolean;
  399.     function UpdatePositions: boolean;
  400.     function FindContact(Number: WideString): WideString; overload;
  401.     function FindContact(FullName: WideString; var AContact: PContactData): boolean; overload;
  402.     function FindContact(FullName: WideString; var ANode: PVirtualNode): boolean; overload;
  403.     { Edit contact. NewNumber and ContactData are for external (not Phonebook) contacts, as Own card, and
  404.       SIM contacts editing. NewNumber is default mobile number when creating new contact. }
  405.     function DoEdit(AsNew: boolean = False; NewNumber: string = ''; ContactData: PContactData = nil): boolean;
  406.   end;
  407.  
  408. function NewGUID: TGUID;
  409.  
  410. function MigrateContact(OldContact: PContactData; var NewContact: PContactData): boolean;
  411.  
  412. function vCard2Contact(VCard: TVCard; contact: PContactData): boolean;
  413. function Contact2vCard(contact: PContactData; var VCard: TVCard): boolean;
  414.  
  415. function NumPos2Str(Pos: TNumberPos): string;
  416. function NumPosEmpty(contact: PContactData): boolean;
  417.  
  418. function GetvCardFullName(VCard: TVCard): WideString;
  419.  
  420. function GetContactDisplayName(contact: PContactData; FamilyFirst: boolean = False): WideString;
  421. function GetContactFullName(contact: PContactData; FamilyFirst: boolean = False): WideString;
  422. procedure SetContactFullName(contact: PContactData; FullName: WideString);
  423. function GetContactDefPhone(contact: PContactData): string;
  424. function GetContactPictureFile(contact: PContactData): string;
  425. function GetContactSoundFile(contact: PContactData): string;
  426.  
  427. function GetContactFmaid(contact: PContactData): string;
  428. function GetContactNotes(contact: PContactData; Notes: TTntStrings): boolean;
  429. function SetContactNotes(contact: PContactData; Notes: TTntStrings): boolean;
  430. function GetContactDetails(contact: PContactData; Notes: TTntStrings): boolean;
  431.  
  432. function IsContactPhone(contact: PContactData; Phone: string): boolean;
  433. function GetContactPhoneType(contact: PContactData; Phone: string): string;
  434. function ReplaceContactDefPhone(contact: PContactData; Phone: string): string;
  435.  
  436. function ContactDefPosition(contact: PContactData; SetPosition: integer = -1): integer;
  437. function GetContactPosition(contact: PContactData; Phone: string): integer;
  438. procedure SetContactPosition(contact: PContactData; Phone: string; SetPosition: integer);
  439.  
  440. implementation
  441.  
  442. {$R *.dfm}
  443.  
  444. { Utils }
  445.  
  446. uses WebUtil, DateUtils, Unicode, Unit1, uGlobal, uConnProgress, uEditContact,
  447.   ComCtrls, TntComCtrls, uAddToGroup, uStatusDlg, IniFiles, uDebug, uXML;
  448.  
  449. function NewGUID: TGUID;
  450. begin
  451.   CreateGUID(Result);
  452. end;
  453.  
  454. function IsContactPhone(contact: PContactData; Phone: string): boolean;
  455. var
  456.   a,b: string;
  457. begin
  458.   Result := False;
  459.   a := Form1.GetPartialNumber(Phone);
  460.   with contact^ do begin
  461.     b := Form1.GetPartialNumber(cell);
  462.     if a = b then begin
  463.       Result := True;
  464.       exit;
  465.     end;
  466.     b := Form1.GetPartialNumber(work);
  467.     if a = b then begin
  468.       Result := True;
  469.       exit;
  470.     end;
  471.     b := Form1.GetPartialNumber(home);
  472.     if a = b then begin
  473.       Result := True;
  474.       exit;
  475.     end;
  476.     b := Form1.GetPartialNumber(fax);
  477.     if a = b then begin
  478.       Result := True;
  479.       exit;
  480.     end;
  481.     b := Form1.GetPartialNumber(other);
  482.     if a = b then begin
  483.       Result := True;
  484.     end;
  485.   end;
  486. end;
  487.  
  488. { This will update DisplayName according to changes in NewContact and settings in OldContact,
  489.   and all other FMA specific settings }
  490. function MigrateContact(OldContact: PContactData; var NewContact: PContactData): boolean;
  491. begin
  492.   with TfrmEditContact.Create(nil) do
  493.     try
  494.       contact := OldContact^;
  495.       LoadAndMergeWith(NewContact^);
  496.       { Migrate contact Fma internal settings }
  497.       NewContact^.displayname := txtDisplayAs.Text; // get newly generated display name
  498.       NewContact^.DefaultIndex := OldContact^.DefaultIndex;
  499.       NewContact^.sound := OldContact^.sound;
  500.       NewContact^.picture := OldContact^.picture;
  501.       NewContact^.CDID := OldContact^.CDID;
  502.       // - Do not copy old LUID sync id //NewContact^.LUID := OldContact^.LUID;
  503.       // - Do not copy old phone positions //NewContact^.Position := OldContact^.Position;
  504.       Result := True;
  505.     finally
  506.       Free;
  507.     end;
  508. end;
  509.  
  510. function GetContactFmaid(contact: PContactData): string;
  511. begin
  512.   Result := Format('{%s}-%s',[contact^.LUID,GUIDToString(contact^.CDID)]);
  513. end;
  514.  
  515. function GetContactDetails(contact: PContactData; Notes: TTntStrings): boolean;
  516. var
  517.   s: string;
  518. begin
  519.   Notes.Clear;
  520.   case contact^.StateIndex of
  521.     0: s := 'New';
  522.     1: s := 'Modified';
  523.     2: s := 'Deleted';
  524.     3: s := 'Unmodified';
  525.   end;
  526.   Notes.Add('State: '+s+' contact');
  527.   Notes.Add('Full name: '+getcontactfullname(contact));
  528.   Notes.Add('Display name: '+contact^.displayname);
  529.   Notes.Add('Title: '+contact^.title);
  530.   Notes.Add('Company: '+contact^.org);
  531.   Notes.Add('E-mail: '+contact^.email);
  532.   Notes.Add('Home phone: '+contact^.home);
  533.   Notes.Add('Work phone: '+contact^.work);
  534.   Notes.Add('Cell phone: '+contact^.cell);
  535.   Notes.Add('Fax number: '+contact^.fax);
  536.   Notes.Add('Other phone: '+contact^.other);
  537.   Notes.Add('Picture File: '+contact^.picture);
  538.   Notes.Add('Ringing Tone: '+contact^.sound);
  539.   case contact^.DefaultIndex of
  540.     0: s := 'None';
  541.     1: s := 'Cell';
  542.     2: s := 'Work';
  543.     3: s := 'Home';
  544.     4: s := 'Other';
  545.   end;
  546.   Notes.Add('Default phone: '+s);
  547.   Result := True;
  548. end;
  549.  
  550. function GetContactNotes(contact: PContactData; Notes: TTntStrings): boolean;
  551. var
  552.   DBName,Section: string;
  553.   sl: TStringList;
  554.   i: integer;
  555. begin
  556.   Section := GetContactFmaid(contact);
  557.   DBName := Form1.GetDatabasePath+'CallNotes.dat';
  558.   if not FileExists(DBName) then
  559.     with TFileStream.Create(DBName,fmCreate) do Free;
  560.   with TIniFile.Create(DBName) do
  561.     try
  562.       sl := TStringList.Create;
  563.       try
  564.         ReadSection(Section,sl);
  565.         Notes.Clear;
  566.         for i := 0 to sl.Count-1 do
  567.           Notes.Add(ReadString(Section,sl[i],''));
  568.       finally
  569.         sl.Free;
  570.       end;
  571.       Result := True;
  572.     finally
  573.       Free;
  574.     end;
  575. end;
  576.  
  577. function SetContactNotes(contact: PContactData; Notes: TTntStrings): boolean;
  578. var
  579.   DBName,Section: string;
  580.   i: integer;
  581. begin
  582.   Section := GetContactFmaid(contact);
  583.   DBName := Form1.GetDatabasePath+'CallNotes.dat';
  584.   if not FileExists(DBName) then
  585.     with TFileStream.Create(DBName,fmCreate) do Free;
  586.   with TIniFile.Create(DBName) do
  587.     try
  588.       EraseSection(Section);
  589.       for i := 0 to Notes.Count-1 do
  590.         WriteString(Section,IntToStr(i),Notes[i]);
  591.       Result := True;
  592.     finally
  593.       Free;
  594.     end;
  595. end;
  596.  
  597. function vCard2Contact(VCard: TVCard; contact: PContactData): boolean;
  598. var
  599.   sl: TTntStringList;
  600. begin
  601.   Result := True;
  602.   FillChar(contact^,SizeOf(contact^),0);
  603.   contact^.title := VCard.title;
  604.   contact^.name := VCard.name;
  605.   contact^.surname := VCard.surname;
  606.   contact^.displayname := VCard.DisplayName;
  607.   contact^.org := VCard.org;
  608.   contact^.email := VCard.email;
  609.   contact^.home := VCard.telhome;
  610.   contact^.work := VCard.telwork;
  611.   contact^.cell := VCard.telcell;
  612.   contact^.fax := VCard.telfax;
  613.   contact^.other := VCard.telother;
  614.   // DefaultIndex = 0 none;1 cell;2 work;3 home;4 other
  615.   if VCard.TelPref = 'M' then
  616.     contact^.DefaultIndex := 1
  617.   else if VCard.TelPref = 'W' then
  618.     contact^.DefaultIndex := 2
  619.   else if VCard.TelPref = 'H' then
  620.     contact^.DefaultIndex := 3
  621.   else if VCard.TelPref = 'O' then
  622.     contact^.DefaultIndex := 4;
  623.   contact^.LUID := VCard.LUID;
  624.   try
  625.     if VCard.UID <> '' then
  626.       contact.CDID := StringToGUID('{'+VCard.UID+'}')
  627.     else
  628.       contact.CDID := NewGUID;
  629.   except
  630.     contact.CDID := NewGUID;
  631.   end;
  632.   sl := TTntStringList.Create;
  633.   try
  634.     sl.Text := VCard.Notes;
  635.     SetContactNotes(contact,sl);
  636.   finally
  637.     sl.Free;
  638.   end;
  639. end;
  640.  
  641. function Contact2vCard(contact: PContactData; var VCard: TVCard): boolean;
  642. var
  643.   sl: TTntStringList;
  644. begin
  645.   Result := True;
  646.   VCard.Clear;
  647.   VCard.title := contact^.title;
  648.   VCard.name := contact^.name;
  649.   VCard.surname := contact^.surname;
  650.   VCard.DisplayName := contact^.displayname;
  651.   VCard.org := contact^.org;
  652.   VCard.email := contact^.email;
  653.   VCard.telhome := contact^.home;
  654.   VCard.telwork := contact^.work;
  655.   VCard.telcell := contact^.cell;
  656.   VCard.telfax := contact^.fax;
  657.   VCard.telother := contact^.other;
  658.   // DefaultIndex = 0 none;1 cell;2 work;3 home;4 other
  659.   case contact^.DefaultIndex of
  660.     1: VCard.TelPref := 'M';
  661.     2: VCard.TelPref := 'W';
  662.     3: VCard.TelPref := 'H';
  663.     4: VCard.TelPref := 'O';
  664.   end;
  665.   VCard.LUID := contact^.LUID;
  666.   try
  667.     VCard.UID := GUIDToString(contact^.CDID);
  668.   except
  669.     VCard.UID := GUIDToString(NewGUID);
  670.   end;
  671.   { remove TGUID brackets }
  672.   if (length(VCard.UID) > 1) and (VCard.UID[1] = '{') then begin
  673.     Delete(VCard.UID,1,1); // {
  674.     Delete(VCard.UID,length(VCard.UID),1); // }
  675.   end;
  676.   sl := TTntStringList.Create;
  677.   try
  678.     GetContactNotes(contact,sl);
  679.     VCard.Notes := sl.Text;
  680.   finally
  681.     sl.Free;
  682.   end;
  683.   { TODO: Add better Modified date support }
  684.   VCard.ModifiedDate := Now;
  685.   //debug
  686.   if Form1.Memo2.Visible then begin
  687.     Form1.Memo2.Lines.Clear;
  688.     Form1.Memo2.Lines.AddStrings(VCard.Raw);
  689.   end;
  690. end;
  691.  
  692. function NumPos2Str(Pos: TNumberPos): string;
  693. begin
  694.   Result := IntToStr(Pos.home) + ',' + IntToStr(Pos.work) +
  695.     ',' + IntToStr(Pos.cell) + ',' + IntToStr(Pos.fax) +
  696.     ',' + IntToStr(Pos.other);
  697. end;
  698.  
  699. function NumPosEmpty(contact: PContactData): boolean;
  700. begin
  701.   Result := ((contact.Position.home = 0) and (contact.home <> '')) or
  702.     ((contact.Position.work = 0) and (contact.work <> '')) or
  703.     ((contact.Position.cell = 0) and (contact.cell <> '')) or
  704.     ((contact.Position.fax = 0) and (contact.fax <> '')) or
  705.     ((contact.Position.other = 0) and (contact.other <> ''));
  706. end;
  707.  
  708. function GetContactPictureFile(contact: PContactData): string;
  709. var
  710.   s: string;
  711. begin
  712.   s := '';
  713.   if contact.picture <> '' then begin
  714.     s := ExePath+'data\'+Form1.PhoneIdentity+'\pic\'+contact.picture;
  715.     if not FileExists(s) then s := '';
  716.   end;
  717.   Result := s;
  718. end;
  719.  
  720. function GetContactSoundFile(contact: PContactData): string;
  721. var
  722.   s: string;
  723. begin
  724.   s := '';
  725.   if contact.sound <> '' then begin
  726.     s := ExePath+'data\'+Form1.PhoneIdentity+'\snd\'+contact.sound;
  727.     if not FileExists(s) then s := '';
  728.   end;
  729.   Result := s;
  730. end;
  731.  
  732. function GetvCardFullName(VCard: TVCard): WideString;
  733. begin
  734.   with VCard do begin
  735.     Result := name;
  736.     if surname <> '' then Result := Result + ' ' + surname;
  737.   end;
  738. end;
  739.  
  740. function GetContactDisplayName(contact: PContactData; FamilyFirst: boolean = False): WideString;
  741. var
  742.   s: WideString;
  743. begin
  744.   s := Trim(contact^.displayname);
  745.   if s = '' then s := GetContactFullName(contact,FamilyFirst);
  746.   Result := s;
  747. end;
  748.  
  749. function GetContactFullName(contact: PContactData; FamilyFirst: boolean): WideString;
  750. begin
  751.   with contact^ do begin
  752.     if FamilyFirst then begin
  753.       Result := surname;
  754.       if name <> '' then Result := Result + ' ' + name;
  755.     end
  756.     else begin
  757.       Result := name;
  758.       if surname <> '' then Result := Result + ' ' + surname;
  759.     end;
  760.   end;
  761.   Result := Trim(Result);
  762. end;
  763.  
  764. procedure SetContactFullName(contact: PContactData; FullName: WideString);
  765. var
  766.   i,j: integer;
  767. begin
  768.   with contact^ do begin
  769.     j := Length(FullName);
  770.     i := j;
  771.     while (i <> 0) and (FullName[i] <> ' ') do dec(i);
  772.     if i = 0 then i := j+1;
  773.     name := Copy(FullName,1,i-1);
  774.     surname := Copy(FullName,i+1,j);
  775.   end;
  776. end;
  777.  
  778. function GetContactPosition(contact: PContactData; Phone: string): integer;
  779. begin
  780.   Result := -1;
  781.   with contact^ do begin
  782.     if home = Phone then Result := Position.home;
  783.     if work = Phone then Result := Position.work;
  784.     if cell = Phone then Result := Position.cell;
  785.     if fax = Phone then Result := Position.fax;
  786.     if other = Phone then Result := Position.other;
  787.   end;
  788. end;
  789.  
  790. procedure SetContactPosition(contact: PContactData; Phone: string; SetPosition: integer);
  791. begin
  792.   with contact^ do begin
  793.     if home = Phone then Position.home := SetPosition;
  794.     if work = Phone then Position.work := SetPosition;
  795.     if cell = Phone then Position.cell := SetPosition;
  796.     if fax = Phone then Position.fax := SetPosition;
  797.     if other = Phone then Position.other := SetPosition;
  798.   end;
  799. end;
  800.  
  801. function ContactDefPosition(contact: PContactData; SetPosition: integer): integer;
  802.   procedure FindFirstGood;
  803.   begin
  804.     with contact^ do begin
  805.       if cell <> '' then begin
  806.         if SetPosition > 0 then Position.cell := SetPosition;
  807.         if Position.cell > 0 then Result := Position.cell;
  808.       end else
  809.       if work <> '' then begin
  810.         if SetPosition > 0 then Position.work := SetPosition;
  811.         if Position.work > 0 then Result := Position.work;
  812.       end else
  813.       if home <> '' then begin
  814.         if SetPosition > 0 then Position.home := SetPosition;
  815.         if Position.home > 0 then Result := Position.home;
  816.       end else
  817.       if other <> '' then begin
  818.         if SetPosition > 0 then Position.other := SetPosition;
  819.         if Position.other > 0 then Result := Position.other;
  820.       end;
  821.     end;
  822.   end;
  823. begin
  824.   Result := -1;
  825.   with contact^ do case DefaultIndex of
  826.     1: if cell <> '' then begin
  827.          if SetPosition > 0 then Position.cell := SetPosition;
  828.          if Position.cell > 0 then Result := Position.cell;
  829.        end;
  830.     2: if work <> '' then begin
  831.          if SetPosition > 0 then Position.work := SetPosition;
  832.          if Position.work > 0 then Result := Position.work;
  833.        end;
  834.     3: if home <> '' then begin
  835.          if SetPosition > 0 then Position.home := SetPosition;
  836.          if Position.home > 0 then Result := Position.home;
  837.        end;
  838.     4: if other <> '' then begin
  839.          if SetPosition > 0 then Position.other := SetPosition;
  840.          if Position.other > 0 then Result := Position.other;
  841.        end;
  842.   end;
  843.   if Result = -1 then FindFirstGood;
  844. end;
  845.  
  846. function GetContactDefPhone(contact: PContactData): string;
  847.   procedure FindFirstNumber;
  848.   begin
  849.     with contact^ do begin
  850.       if cell <> '' then begin
  851.         Result := cell;
  852.       end else
  853.       if work <> '' then begin
  854.         Result := work;
  855.       end else
  856.       if home <> '' then begin
  857.         Result := home;
  858.       end else
  859.       if other <> '' then begin
  860.         Result := other;
  861.       end;
  862.     end;
  863.   end;
  864. begin
  865.   Result := '';
  866.   with contact^ do case DefaultIndex of
  867.     1: Result := cell;
  868.     2: Result := work;
  869.     3: Result := home;
  870.     4: Result := other;
  871.   end;
  872.   if Result = '' then FindFirstNumber;
  873. end;
  874.  
  875. function ReplaceContactDefPhone(contact: PContactData; Phone: string): string;
  876.   procedure FindFirstNumber;
  877.   begin
  878.     with contact^ do begin
  879.       if cell <> '' then begin
  880.         cell := Phone;
  881.       end else
  882.       if work <> '' then begin
  883.         work := Phone;
  884.       end else
  885.       if home <> '' then begin
  886.         home := Phone;
  887.       end else
  888.       if other <> '' then begin
  889.         other := Phone;
  890.       end;
  891.     end;
  892.   end;
  893. begin
  894.   Result := GetContactDefPhone(contact);
  895.   with contact^ do case DefaultIndex of
  896.     1: cell := Phone;
  897.     2: work := Phone;
  898.     3: home := Phone;
  899.     4: other := Phone;
  900.     else FindFirstNumber;
  901.   end;
  902. end;
  903.  
  904. function GetContactPhoneType(contact: PContactData; Phone: string): string;
  905. begin
  906.   Result := '';
  907.   with contact^ do begin
  908.     if cell = Phone then begin
  909.       Result := 'M';
  910.     end else
  911.     if work = Phone then begin
  912.       Result := 'W';
  913.     end else
  914.     if home = Phone then begin
  915.       Result := 'H';
  916.     end else
  917.     if fax = Phone then begin
  918.       Result := 'F';
  919.     end else
  920.     if other = Phone then begin
  921.       Result := 'O';
  922.     end;
  923.   end;
  924. end;
  925.  
  926. { TfrmSyncPhonebook }
  927.  
  928. constructor TfrmSyncPhonebook.Create(AOwner: TComponent);
  929. begin
  930.   inherited;
  931.   VCard := TVCard.Create;
  932.   FMaxRecME := 510; FMaxNameLen := 180; FMaxTelLen := 80;
  933. end;
  934.  
  935. destructor TfrmSyncPhonebook.Destroy;
  936. begin
  937.   VCard.Free;
  938.   inherited;
  939. end;
  940.     
  941. procedure TfrmSyncPhonebook.ListContactsGetText(Sender: TBaseVirtualTree;
  942.   Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType;
  943.   var CellText: WideString);
  944. var
  945.   contact: PContactData;
  946. begin
  947.   contact := Sender.GetNodeData(Node);
  948.  
  949.   if Column = 0 then
  950.     case contact.StateIndex of //0 new entry;1 modified entry;2 deleted entry;3 normal entry
  951.       0: CellText := 'New';
  952.       1: CellText := 'Mod';
  953.       2: CellText := 'Del';
  954.       else CellText := '';
  955.     end
  956.   else if Column = 1 then CellText := GetContactDisplayName(contact,LastFirst1.Checked)
  957.   else if Column = 2 then CellText := contact.title
  958.   else if Column = 3 then CellText := contact.org
  959.   else if Column = 4 then CellText := contact.email
  960.   else if Column = 5 then CellText := contact.home
  961.   else if Column = 6 then CellText := contact.work
  962.   else if Column = 7 then CellText := contact.cell
  963.   else if Column = 8 then CellText := contact.fax
  964.   else if Column = 9 then CellText := contact.other
  965. end;
  966.  
  967. procedure TfrmSyncPhonebook.ListContactsHeaderClick(Sender: TVTHeader;
  968.   Column: TColumnIndex; Button: TMouseButton; Shift: TShiftState; X,
  969.   Y: Integer);
  970. begin
  971.   if Button = mbLeft then begin
  972.     if Column = Sender.SortColumn then begin
  973.       if Sender.SortDirection = sdDescending then
  974.         Sender.SortDirection := sdAscending
  975.       else
  976.         Sender.SortDirection := sdDescending;
  977.     end
  978.     else
  979.       Sender.SortColumn := Column;
  980.     ListContacts.Sort(nil, ListContacts.Header.SortColumn, ListContacts.Header.SortDirection);
  981.   end;
  982. end;
  983.  
  984. procedure TfrmSyncPhonebook.ListContactsCompareNodes(Sender: TBaseVirtualTree;
  985.   Node1, Node2: PVirtualNode; Column: TColumnIndex; var Result: Integer);
  986. var
  987.   contact1, contact2: PContactData;
  988. begin
  989.   contact1 := Sender.GetNodeData(Node1);
  990.   contact2 := Sender.GetNodeData(Node2);
  991.   
  992.   if Column = 0 then begin
  993.     if contact1.StateIndex > contact2.StateIndex then
  994.       Result := 1
  995.     else
  996.       if contact1.StateIndex < contact2.StateIndex then
  997.         Result := -1
  998.       else
  999.         Result := 0;  
  1000.   end
  1001.   else if Column = 1 then
  1002.     Result := WideCompareStr(GetContactDisplayName(contact1,LastFirst1.Checked),
  1003.                              GetContactDisplayName(contact2,LastFirst1.Checked))
  1004.   else if Column = 2 then Result := WideCompareStr(contact1.title, contact2.title)
  1005.   else if Column = 3 then Result := WideCompareStr(contact1.org,   contact2.org)
  1006.   else if Column = 4 then Result := WideCompareStr(contact1.email, contact2.email)
  1007.   else if Column = 5 then Result := WideCompareStr(contact1.home,  contact2.home)
  1008.   else if Column = 6 then Result := WideCompareStr(contact1.work,  contact2.work)
  1009.   else if Column = 7 then Result := WideCompareStr(contact1.cell,  contact2.cell)
  1010.   else if Column = 8 then Result := WideCompareStr(contact1.fax,   contact2.fax)
  1011.   else if Column = 9 then Result := WideCompareStr(contact1.other, contact2.other)
  1012. end;
  1013.  
  1014. procedure TfrmSyncPhonebook.ListContactsGetImageIndex(Sender: TBaseVirtualTree;
  1015.   Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex;
  1016.   var Ghosted: Boolean; var ImageIndex: Integer);
  1017. var
  1018.   contact: PContactData;
  1019. begin
  1020.   if Column = 0 then begin
  1021.     if (Kind = ikNormal) or (Kind = ikSelected) then begin
  1022.       contact := Sender.GetNodeData(Node);
  1023.       { Yesllow icon on personalized contacts, blue on others }
  1024.       if (contact.StateIndex = 3) and ((contact.picture <> '') or (contact.sound <> '')) then
  1025.         ImageIndex := 4
  1026.       else
  1027.         ImageIndex := contact.StateIndex;
  1028.     end
  1029.     else ImageIndex := -1;
  1030.   end;
  1031. end;
  1032.  
  1033. procedure TfrmSyncPhonebook.btnEditClick(Sender: TObject);
  1034. var
  1035.   Node :PVirtualNode;
  1036. begin
  1037.   Node := ListContacts.FocusedNode;
  1038.   if Node <> nil then begin
  1039.     Selcontact := ListContacts.GetNodeData(Node);
  1040.     FUndoIndx := Node.Index;
  1041.     txtLUID.Text := Selcontact.LUID + ' - ' + inttostr(Selcontact.StateIndex);
  1042.     DoEdit;
  1043.   end;
  1044. end;
  1045.  
  1046. procedure TfrmSyncPhonebook.RenderListView(const sl: TStrings);
  1047. var
  1048.   i: Integer;
  1049.   contact: PContactData;
  1050.   Node: PVirtualNode;
  1051.   s: String;
  1052. begin
  1053.   ListContacts.BeginUpdate;
  1054.   try
  1055.     ListContacts.Clear;
  1056.     ListContacts.NodeDataSize := sizeof(TContactData);
  1057.     i := 0;
  1058.     while i < sl.Count do begin
  1059.       s := sl.Strings[i];
  1060.       while (not EvenQuotes(s)) and (i < sl.Count - 2) do begin
  1061.         Inc(i);
  1062.         s := s + #13#10 + sl.Strings[i];
  1063.       end;
  1064.  
  1065.       if Pos('LOG:', s) = 1 then begin
  1066.          CC := Copy(s, Pos(':', s) + 1, length(s));
  1067.          txtCC.Text := CC;
  1068.          Break;
  1069.       end;
  1070.       Node := ListContacts.AddChild(nil);
  1071.       contact := ListContacts.GetNodeData(Node);
  1072.       try
  1073.         { Enter data }
  1074.         contact.title := HTMLDecode(GetToken(s,1));
  1075.         contact.name := HTMLDecode(GetToken(s,2));
  1076.         contact.surname := HTMLDecode(GetToken(s,3));
  1077.         contact.org := HTMLDecode(GetToken(s,4));
  1078.         contact.email := HTMLDecode(GetToken(s,5));
  1079.         contact.home := HTMLDecode(GetToken(s,6));
  1080.         contact.work := HTMLDecode(GetToken(s,7));
  1081.         contact.cell := HTMLDecode(GetToken(s,8));
  1082.         contact.fax := HTMLDecode(GetToken(s,9));
  1083.         contact.other := HTMLDecode(GetToken(s,10));
  1084.         contact.LUID := GetToken(s,11);
  1085.         contact.stateindex := strtoint(GetToken(s,0));
  1086.         { Get number positions }
  1087.         FillChar(contact.Position,SizeOf(contact.Position),0);
  1088.         contact.Position.home := StrToInt(GetToken(s,12));
  1089.         contact.Position.work := StrToInt(GetToken(s,13));
  1090.         contact.Position.cell := StrToInt(GetToken(s,14));
  1091.         contact.Position.fax := StrToInt(GetToken(s,15));
  1092.         contact.Position.other := StrToInt(GetToken(s,16));
  1093.         { Get default number }
  1094.         contact.DefaultIndex := StrToInt(GetToken(s,17));
  1095.         { Get personalization }
  1096.         contact.picture := HTMLDecode(GetToken(s,18));
  1097.         contact.sound := HTMLDecode(GetToken(s,19));
  1098.         try
  1099.           { Outlook sync ID field }
  1100.           contact.CDID := StringToGUID(GetToken(s,20));
  1101.         except
  1102.           contact.CDID := NewGUID;
  1103.         end;
  1104.         { Display Name }
  1105.         contact.displayname := HTMLDecode(GetToken(s,21));
  1106.       except
  1107.         on E: Exception do
  1108.           Form1.Debug('CONTACTS SYNC DB ERROR (' + E.Message + '): '+s);
  1109.       end;
  1110.  
  1111.       Inc(i);
  1112.     end;
  1113.     RenderGUIDs;
  1114.   finally
  1115.     ListContacts.EndUpdate;
  1116.     ListContacts.Sort(nil, ListContacts.Header.SortColumn, ListContacts.Header.SortDirection);
  1117.     ListContacts.Update;
  1118.     UndoLastChange1.Enabled := False;
  1119.   end;
  1120. end;
  1121.  
  1122. procedure TfrmSyncPhonebook.SaveContacts(FileName:WideString);
  1123. var
  1124.   sl: TStrings;
  1125.   Node: PVirtualNode;
  1126.   contact: PContactData;
  1127.   str: String;
  1128. begin
  1129.   sl := TStringList.Create;
  1130.   try
  1131.     with ListContacts do begin
  1132.       Node := GetFirst;
  1133.       if Node <> nil then repeat
  1134.         try
  1135.           contact := GetNodeData(node);
  1136.           str := inttostr(contact.StateIndex);
  1137.           str := str + ',"' + HTMLEncode(contact.title,False);
  1138.           str := str + '","' + HTMLEncode(contact.name,False);
  1139.           str := str + '","' + HTMLEncode(contact.surname,False);
  1140.           str := str + '","' + HTMLEncode(contact.org,False);
  1141.           str := str + '","' + HTMLEncode(contact.email,False);
  1142.           str := str + '","' + HTMLEncode(contact.home,False);
  1143.           str := str + '","' + HTMLEncode(contact.work,False);
  1144.           str := str + '","' + HTMLEncode(contact.cell,False);
  1145.           str := str + '","' + HTMLEncode(contact.fax,False);
  1146.           str := str + '","' + HTMLEncode(contact.other,False);
  1147.           str := str + '",' + contact.LUID;
  1148.           str := str + ',' + NumPos2Str(contact.Position);
  1149.           str := str + ',' + IntToStr(contact.DefaultIndex);
  1150.           str := str + ',"' + HTMLEncode(contact.picture,False);
  1151.           str := str + '","' + HTMLEncode(contact.sound,False);
  1152.           str := str + '",' + GUIDToString(contact.CDID);
  1153.           str := str + ',"' + HTMLEncode(contact.displayname,False) + '"';
  1154.           sl.Add(str);
  1155.         except
  1156.         end;
  1157.         Node := GetNext(Node);
  1158.       until Node = nil;
  1159.     end;
  1160.     sl.add('LOG:' + CC);
  1161.     sl.SaveToFile(FileName);
  1162.     sl.Clear;
  1163.     sl.Add(IntToStr(FMaxRecME));
  1164.     sl.Add(IntToStr(FMaxNameLen));
  1165.     sl.Add(IntToStr(FMaxTitleLen));
  1166.     sl.Add(IntToStr(FMaxOrgLen));
  1167.     sl.Add(IntToStr(FMaxMailLen));
  1168.     sl.Add(IntToStr(FMaxTellen));
  1169.     sl.SaveToFile(ChangeFileExt(FileName,'MAX.dat'));
  1170.   finally
  1171.     sl.Free;
  1172.   end;  
  1173. end;
  1174.  
  1175. procedure TfrmSyncPhonebook.LoadContacts(FileName:WideString);
  1176. var
  1177.   sl : TStringList;
  1178. begin
  1179.   ListContacts.NodeDataSize := sizeof(TContactData);
  1180.   //Force primary sort column ... i don't why!??!
  1181.   //ListContacts.Header.SortColumn := 1;
  1182.   sl := TStringList.Create;
  1183.   try
  1184.     try
  1185.       sl.LoadFromFile(FileName);
  1186.     except
  1187.     end;
  1188.     RenderListView(sl);
  1189.     FMaxRecME := 510;
  1190.     FMaxNameLen := 30;
  1191.     FMaxTitleLen := 15;
  1192.     FMaxOrgLen := 15;
  1193.     FMaxMailLen := 50;
  1194.     FMaxTellen := 40;
  1195.     try
  1196.       sl.LoadFromFile(ChangeFileExt(FileName,'MAX.dat'));
  1197.       FMaxRecME := StrToInt(sl[0]);
  1198.       FMaxNameLen := StrToInt(sl[1]);
  1199.       FMaxTitleLen := StrToInt(sl[2]);
  1200.       FMaxOrgLen := StrToInt(sl[3]);
  1201.       FMaxMailLen := StrToInt(sl[4]);
  1202.       FMaxTellen := StrToInt(sl[5]);
  1203.     except
  1204.     end;
  1205.   finally
  1206.     sl.Free;
  1207.   end;
  1208. end;
  1209.  
  1210. procedure TfrmSyncPhonebook.btnSYNCClick(Sender: TObject);
  1211. var
  1212.   isModified: Boolean;
  1213.   dlg: TfrmConnect;
  1214. begin
  1215.   FSyncConflict := Form1.FSyncConflict;
  1216.  
  1217.   btnSync.Enabled := False;
  1218.   Form1.ActionSyncPhonebook.Enabled := False;
  1219.  
  1220.   dlg := GetProgressDialog;
  1221.   try
  1222.     if Form1.CanShowProgress then
  1223.       dlg.ShowProgress(Form1.FProgressLongOnly);
  1224.     dlg.SetDescr('Synchronizing phonebook contacts');
  1225.     Form1.Status('Start Sync Phonebook....');
  1226.     SyncLog('Sync Phonebook started.');
  1227.     VCard.clear;
  1228.     try
  1229.       //Start the sync process
  1230.       isModified := Synchronize;
  1231.       //Force refresh phoneBook
  1232.       if isModified then begin
  1233.         dlg.SetDescr('Refreshing local phonebook');
  1234.         Form1.RefreshPhoneBook;
  1235.       end;
  1236.       //ShowMessage('Sync Phonebook completed');
  1237.       Form1.Status('Sync Phonebook completed.');
  1238.       SyncLog('Sync Phonebook completed.');
  1239.     except
  1240.       ShowMessage('Error: synchronize aborted');
  1241.       SyncLog('Error: synchronize aborted');
  1242.     end;
  1243.   finally
  1244.     FreeProgressDialog;
  1245.     btnSync.Enabled := True;
  1246.     ListContacts.Sort(nil, ListContacts.Header.SortColumn, ListContacts.Header.SortDirection);
  1247.     ListContacts.Update;
  1248.     Form1.UpdateMEPhonebook;
  1249.     Form1.ActionSyncPhonebook.Enabled := True;
  1250.   end;
  1251. end;
  1252.  
  1253. procedure TfrmSyncPhonebook.btnNEWClick(Sender: TObject);
  1254. begin
  1255.   if ListContacts.ChildCount[nil] >= FMaxRecME then begin
  1256.     ShowMessage('No more space in pb memory!' + #13 + #10 + 'I''m sorry.');
  1257.     Exit;
  1258.   end;
  1259.   DoEdit(True);
  1260. end;
  1261.  
  1262. procedure TfrmSyncPhonebook.btnDELClick(Sender: TObject);
  1263. var
  1264.   Node,Tmp: PVirtualNode;
  1265.   contact :PContactData;
  1266.   oldState :Integer;
  1267.   s: string;
  1268. begin
  1269.   if ListContacts.SelectedCount = 0 then exit;
  1270.   s := 'Deleting ' + IntToStr(ListContacts.SelectedCount) + ' item(s)';
  1271.   if MessageDlg(s+'. Do you wish to continue?',mtConfirmation,[mbYes,mbNo],0) <> ID_YES then
  1272.     exit;
  1273.   Form1.Status(s+'...');
  1274.   State := 2;
  1275.   ListContacts.BeginUpdate;
  1276.   try
  1277.     node := ListContacts.GetFirst;
  1278.     while node <> nil do begin
  1279.       if ListContacts.Selected[node] then begin
  1280.         contact := ListContacts.GetNodeData(Node);
  1281.         oldState := contact.stateindex;
  1282.         contact.stateindex := State;
  1283.         if oldState = 0 then begin
  1284.           Tmp := Node;
  1285.           if Node <> ListContacts.GetFirst then begin
  1286.             Node := ListContacts.GetPrevious(Node);
  1287.             ListContacts.DeleteNode(Tmp);
  1288.           end
  1289.           else begin
  1290.             ListContacts.DeleteNode(Tmp);
  1291.             Node := ListContacts.GetFirst;
  1292.             continue;
  1293.           end;
  1294.         end;
  1295.       end;
  1296.       node := ListContacts.GetNext(Node);
  1297.     end;
  1298.   finally
  1299.     ListContacts.EndUpdate;
  1300.     Form1.Status('');
  1301.     Form1.UpdateMEPhonebook;
  1302.   end;
  1303. end;
  1304.  
  1305. function TfrmSyncPhonebook.Synchronize: boolean; // True if any change is made!
  1306. var
  1307.   sl,pl: TStringList;
  1308.   stream : TStream;
  1309.   addPCont : array of widestring;
  1310.   delPCont : array of widestring;
  1311.   j: Integer;
  1312.   Node: PVirtualNode;
  1313.   migrate: TContactData;
  1314.   contact: PContactData;
  1315.   F,LUID : WideString;
  1316.   PhoneOnPc,AsNew: Boolean;
  1317. begin
  1318.   Result := False;
  1319.   if ListContacts.childcount[nil] = 0 then begin
  1320.     Result := FullRefresh;
  1321.     exit;
  1322.   end;
  1323.  
  1324.   //start sync process
  1325.   Form1.ObexConnect('IRMC-SYNC');
  1326.   if not Form1.FConnected then begin
  1327.      ShowMessage('The Sync Phonebook can''t start...try to restart your phone.');
  1328.      SyncLog('The Sync Phonebook can''t start...try to restart your phone.');
  1329.      btnSync.Enabled := True;
  1330.      Form1.ActionSyncPhonebook.Enabled := True;
  1331.      Exit;
  1332.   end;
  1333.  
  1334.   VCard.Clear;
  1335.   sl := TStringList.Create;
  1336.   pl := TStringList.Create;
  1337.   try
  1338.     { if Fma CC is 1020, and phone CC is 1025 the result might be like this:
  1339.       (here for example, we have 3 new and one deleted contacts)
  1340.  
  1341.       SN:351956003653753
  1342.       DID:6D25
  1343.       Total-Records:100
  1344.       Maximum-Records:510
  1345.       M:1022::00003D010000
  1346.       M:1023::000047010000
  1347.       H:1024::0000E4000000
  1348.       M:1025::0000E5000000
  1349.     }
  1350.     //Get all record changes in phone for latest used LOG Number in FMA
  1351.     Form1.ObexGetObject('telecom/pb/luid/' + CC +'.log',pl);
  1352.  
  1353.     // Build lists of localy modified and deleted contacts (on PC)
  1354.     Node := ListContacts.GetFirst;
  1355.     while Node <> nil do begin
  1356.       contact := ListContacts.GetNodeData(node);
  1357.       case contact.StateIndex of
  1358.         0,1: begin // new or modified
  1359.                SetLength(addPCont, length(addPCont) + 1);
  1360.                addPCont[High(addPCont)] := contact.LUID;
  1361.              end;
  1362.           2: begin// deleted
  1363.                SetLength(delPCont, length(delPCont) + 1);
  1364.                delPCont[High(delPCont)] := contact.LUID;
  1365.              end;
  1366.       end;
  1367.       Node := ListContacts.GetNext(Node);
  1368.     end;
  1369.  
  1370.     ListContacts.BeginUpdate;
  1371.     try
  1372.       // first apply phone changes
  1373.       for j := 0 to pl.Count-1 do begin
  1374.         F := '';
  1375.  
  1376.         if Pos('M:', pl[j]) = 1 then begin //entries modified
  1377.           LUID := Copy(pl[j], Pos('::', pl[j]) + 2, length(pl[j]));
  1378.           if LFindContact(LUID,contact) then begin
  1379.             F := GetContactFullName(contact);
  1380.             migrate := contact^;
  1381.             AsNew := False;
  1382.           end
  1383.           else
  1384.             AsNew := True;
  1385.           if CheckInArray(addPCont, LUID) then begin
  1386.             PhoneOnPC := PromptConflict(F, 'is modified on phone and modified on pc.');
  1387.             if not PhoneOnPC then continue; // later will overwrite phone contact
  1388.             // else overwrite local contact
  1389.           end;
  1390.           if CheckInArray(delPCont, LUID) then begin
  1391.             PhoneOnPC := PromptConflict(F, 'is modified on phone and deleted on pc.');
  1392.             if not PhoneOnPC then continue; // later will delete phone contact
  1393.             // else resurrect local contact
  1394.           end;
  1395.           //Get new VCard
  1396.           Form1.ObexGetObject('telecom/pb/luid/' + LUID + '.vcf',sl);
  1397.           VCard.Clear;
  1398.           VCard.Raw := sl;
  1399.           //Remove old VCard 
  1400.           if not AsNew then
  1401.             EraseContact(LUID,False);
  1402.           //Add new Node and Parse VCard
  1403.           Node := ListContacts.AddChild(nil);
  1404.           contact := ListContacts.GetNodeData(Node);
  1405.           vCard2Contact(VCard,contact);
  1406.           contact.stateindex := 3;
  1407.           //Migrate Fma internal settings
  1408.           if not AsNew then 
  1409.             MigrateContact(@migrate,contact);
  1410.           // TODO: add picture and sound support here....
  1411.           if AsNew then
  1412.              SyncLog(GetvCardFullName(VCard) + ' added to FMA by phone.')
  1413.           else
  1414.              SyncLog(GetvCardFullName(VCard) + ' modified in FMA by phone.');
  1415.           VCard.Clear;
  1416.           sl.Clear;
  1417.           // Update LOG Number dinamicaly (current LOG record has been processed)
  1418.           CC := Copy(pl[j],3,Pos('::',pl[j])-3);
  1419.           Result := True;
  1420.         end;
  1421.  
  1422.         if Pos('H:', pl[j]) = 1 then begin  //entries deleted
  1423.           LUID := Copy(pl[j], Pos('::', pl[j]) + 2, length(pl[j]));
  1424.           if LFindContact(LUID,contact) then begin
  1425.             F := GetContactFullName(contact);
  1426.             AsNew := False;
  1427.           end
  1428.           else
  1429.             AsNew := True;
  1430.           if CheckInArray(addPCont, LUID) then begin
  1431.             PhoneOnPC := PromptConflict(F, 'is deleted on phone and modified on pc.');
  1432.             if not PhoneOnPC then continue; // later will resurrect phone contact
  1433.             // else delete local contact
  1434.           end;
  1435.           EraseContact(LUID,False);
  1436.           if CheckInArray(delPCont, LUID) then
  1437.              SyncLog(F + ' deleted in phone by FMA.')
  1438.           else if not AsNew then
  1439.              SyncLog(F + ' deleted in FMA by phone.');
  1440.           // Update LOG Number dinamicaly (current LOG record has been processed)
  1441.           CC := Copy(pl[j],3,Pos('::',pl[j])-3);
  1442.           Result := True;
  1443.         end;
  1444.       end;
  1445.       { well, we have processed all "CC.log" entries and we have updated CC up to latest one }
  1446.       SetLength(addPCont,0);
  1447.       SetLength(delPCont,0);
  1448.  
  1449.       // Build lists of contacts modified and deleted in phone
  1450.       for j := 0 to pl.Count-1 do begin
  1451.          if Pos('M:', pl[j]) = 1 then begin //entries modified
  1452.             SetLength(addPCont, length(addPCont) + 1);
  1453.             addPCont[High(addPCont)] := Copy(pl[j], Pos('::', pl[j]) + 2, length(pl[j]));
  1454.             end
  1455.          else if Pos('H:', pl[j]) = 1 then begin  //entries deleted
  1456.             SetLength(delPCont, length(delPCont) + 1);
  1457.             delPCont[High(delPCont)] := Copy(pl[j], Pos('::', pl[j]) + 2, length(pl[j]));
  1458.          end;
  1459.       end;
  1460.  
  1461.       // next apply PC changes
  1462.       try
  1463.         Node := ListContacts.GetFirst;
  1464.         while Node <> nil do begin
  1465.           contact := ListContacts.GetNodeData(node);
  1466.           if contact.StateIndex <> 3 then begin // skip unmodified contacts
  1467.             Contact2vCard(contact,VCard);
  1468.             // TODO: add picture and sound support here....
  1469.             stream := TMemoryStream.Create;
  1470.             try
  1471.               VCard.Raw.SaveToStream(stream);
  1472.               VCard.Clear;
  1473.               stream.Seek(0, soFromBeginning);
  1474.               F := GetContactFullName(contact);
  1475.               case contact.StateIndex of
  1476.                  0: begin //new
  1477.                       //TODO: check if contact with same name already exists 
  1478.                       contact.LUID := Form1.ObexPutObject('telecom/pb/luid/.vcf', stream); //New LUID
  1479.                       contact.StateIndex := 3; //entries syncronized
  1480.                       SyncLog(F + ' added to phone by FMA.');
  1481.                       Result := True;
  1482.                     end;
  1483.                  1: begin //modified
  1484.                       AsNew := CheckInArray(delPCont, contact.LUID);
  1485.                       if AsNew then begin
  1486.                         contact.LUID := Form1.ObexPutObject('telecom/pb/luid/.vcf', stream); //New LUID
  1487.                         SyncLog(F + ' added to phone by FMA.');
  1488.                       end
  1489.                       else begin
  1490.                         contact.LUID := Form1.ObexPutObject('telecom/pb/luid/' + contact.luid + '.vcf', stream); //Modified LUID
  1491.                         SyncLog(F + ' modified in phone by FMA.');
  1492.                       end;
  1493.                       contact.StateIndex := 3; //entries syncronized
  1494.                       Result := True;
  1495.                     end;
  1496.                  2: begin //deleted
  1497.                       contact.LUID := Form1.ObexPutObject('telecom/pb/luid/' + contact.luid + '.vcf', nil); //deletd LUID
  1498.                       contact.StateIndex := 3; //entries syncronized 
  1499.                       ListContacts.DeleteNode(Node);
  1500.                       SyncLog(F + ' deleted in phone by FMA.');
  1501.                       Result := True;
  1502.                     end;
  1503.               end;
  1504.             finally
  1505.               stream.Free;
  1506.             end;
  1507.           end;
  1508.           Node := ListContacts.GetNext(Node);
  1509.         end;
  1510.       finally
  1511.         // get current LOG Number from phone -- it will include all changes made in FMA that we just apply to phone
  1512.         Form1.ObexGetObject('telecom/pb/luid/cc.log',sl);
  1513.         CC := sl.Strings[0];
  1514.       end;
  1515.     finally
  1516.       RenderGUIDs;
  1517.       ListContacts.EndUpdate;
  1518.     end;
  1519.  
  1520.     {
  1521.       SN:351956003653753
  1522.       DID:6D25
  1523.       Total-Records:100
  1524.       Maximum-Records:510
  1525.       *
  1526.     }
  1527.     // Do we have to perform a full refresh? (too many changes in phone)
  1528.     // Local changes have been applied to the phone already, so we can do
  1529.     // full refresh, if needed...
  1530.     if pl[pl.Count-1] = '*' then begin
  1531.       Result := FullRefresh;
  1532.       exit;
  1533.     end;
  1534.   finally
  1535.     sl.Free;
  1536.     pl.Free;
  1537.     Form1.ObexDisconnect;
  1538.     ListContacts.Sort(nil, ListContacts.Header.SortColumn, ListContacts.Header.SortDirection);
  1539.     ListContacts.Update;
  1540.     UndoLastChange1.Enabled := False;
  1541.   end;
  1542.   Result := Result or UpdatePositions;
  1543. end;
  1544.  
  1545. {Utilities}
  1546.  
  1547. function TfrmSyncPhonebook.CheckInArray(A: array of widestring;
  1548.   S: Widestring): boolean;
  1549. var
  1550.    i:Integer;
  1551. begin
  1552.   for i:=0 to High(A) do begin
  1553.      if A[i] = S then begin
  1554.         Result := True;
  1555.         Exit;
  1556.      end;
  1557.   end;
  1558.   Result := False;
  1559. end;
  1560.  
  1561. {
  1562. procedure TfrmSyncPhonebook.ResetInArray(var A: array of widestring;
  1563.   S: Widestring);
  1564. var
  1565.    i:Integer;
  1566. begin
  1567.   for i:=0 to High(A) do begin
  1568.      if A[i] = S then begin
  1569.         A[i] := '';
  1570.      end;
  1571.   end;
  1572. end;
  1573. }
  1574.  
  1575. function TfrmSyncPhonebook.EraseContact(LUID :Widestring; Log:Boolean):Boolean;
  1576. var
  1577.    Node: PVirtualNode;
  1578.    contact: PContactData;
  1579. begin
  1580.   Result := False;
  1581.   with ListContacts do begin
  1582.     Node := GetFirst;
  1583.     while Node <> nil do begin
  1584.       contact := GetNodeData(node);
  1585.       if LUID = contact.LUID then begin
  1586.          if Log then
  1587.             SyncLog(GetContactFullName(contact) + ' deleted in FMA by phone.');
  1588.          DeleteNode(Node);
  1589.          Result := True;
  1590.          break;
  1591.       end;
  1592.       Node := GetNext(Node);
  1593.     end;
  1594.   end;
  1595. end;
  1596.  
  1597. function TfrmSyncPhonebook.PromptConflict(NameContact: WideString; Info:WideString): boolean;
  1598. begin
  1599.   frmPromptConflict := TfrmPromptConflict.Create(Self);
  1600.   Result := true;
  1601.   With frmPromptConflict do begin
  1602.     lblContact.Caption := NameContact;
  1603.     lblInfo.Caption := Info;
  1604.     if ShowModal = mrOK then begin
  1605.       if grpConflict.ItemIndex = 0 then
  1606.          Result := True
  1607.       else
  1608.          Result := False;
  1609.     end;
  1610.   end;
  1611.  
  1612.   frmPromptConflict.Free;
  1613. end;
  1614.  
  1615. procedure TfrmSyncPhonebook.SyncLog(Desc: WideString);
  1616. begin
  1617.    Form1.SyncLog(Desc);
  1618. end;
  1619.  
  1620. procedure TfrmSyncPhonebook.btnLOGClick(Sender: TObject);
  1621. begin
  1622.   GetSyncLogWindow.Show;
  1623. end;
  1624.  
  1625. procedure TfrmSyncPhonebook.ForceUpdateClick(Sender: TObject);
  1626. begin
  1627.   ForceContact(1);
  1628. end;
  1629.  
  1630. procedure TfrmSyncPhonebook.ExportList(FileType:Integer; Filename: WideString);
  1631. var
  1632.   node: PVirtualNode;
  1633.   contact: PContactData;
  1634.   sl: TStringList;
  1635.   str: WideString;
  1636.   XML: TXML;
  1637. begin
  1638.   case FileType of
  1639.     1:begin//vCard
  1640.         sl := TStringList.Create;
  1641.         with ListContacts do begin
  1642.           node := GetFirst;
  1643.           repeat
  1644.              try
  1645.                 if Selected[node] then begin
  1646.                    contact := GetNodeData(node);
  1647.                    Contact2vCard(contact,VCard);
  1648.                    //TODO: add picture and sound support here....
  1649.                    sl.Clear;
  1650.                    sl.AddSTrings(VCard.Raw);
  1651.                    if ListContacts.SelectedCount <> 1 then begin
  1652.                      str := Trim(GetContactFullName(contact));
  1653.                      str := StringReplace(str,' ','-',[rfReplaceAll]);
  1654.                      str := ChangeFileExt(FileName,'-'+str)+ExtractFileExt(Filename);
  1655.                      sl.SaveToFile(str);
  1656.                    end
  1657.                    else
  1658.                      sl.SaveToFile(Filename);
  1659.                 end;
  1660.              except
  1661.              end;
  1662.              node := GetNext(node);
  1663.           until node = nil;
  1664.         end;
  1665.         sl.Free;
  1666.       end;
  1667.     2:begin//CSV
  1668.         sl := TStringList.Create;
  1669.         str := '"Title","First Name","Last Name","Company","E-mail Address","E-mail Display Name","Home Phone","Business Phone",'+
  1670.           '"Mobile Phone","Business Fax","Other Phone","Primary Phone"';
  1671.         sl.add(str);
  1672.         with ListContacts do begin
  1673.           node := GetFirst;
  1674.           repeat
  1675.              try
  1676.                 if Selected[node] then begin
  1677.                    { Bug 847307 Export to .csv files.
  1678.                      Fixed to use "," instead of ";" and field names compatability with Outlook 2003 fields, which are shown here:
  1679.                      "Title","First Name","Middle Name","Last Name","Suffix","Company","Department","Job Title","Business Street",
  1680.                      "Business Street 2","Business Street 3","Business City","Business State","Business Postal Code","Business Country",
  1681.                      "Home Street","Home Street 2","Home Street 3","Home City","Home State","Home Postal Code","Home Country",
  1682.                      "Other Street","Other Street 2","Other Street 3","Other City","Other State","Other Postal Code","Other Country",
  1683.                      "Assistant's Phone","Business Fax","Business Phone","Business Phone 2","Callback","Car Phone","Company Main Phone",
  1684.                      "Home Fax","Home Phone","Home Phone 2","ISDN","Mobile Phone","Other Fax","Other Phone","Pager","Primary Phone",
  1685.                      "Radio Phone","TTY/TDD Phone","Telex","Account","Anniversary","Assistant's Name","Billing Information","Birthday",
  1686.                      "Business Address PO Box","Categories","Children","Directory Server","E-mail Address","E-mail Type","E-mail Display Name",
  1687.                      "E-mail 2 Address","E-mail 2 Type","E-mail 2 Display Name","E-mail 3 Address","E-mail 3 Type","E-mail 3 Display Name",
  1688.                      "Gender","Government ID Number","Hobby","Home Address PO Box","Initials","Internet Free Busy","Keywords","Language",
  1689.                      "Location","Manager's Name","Mileage","Notes","Office Location","Organizational ID Number","Other Address PO Box",
  1690.                      "Priority","Private","Profession","Referred By","Sensitivity","Spouse","User 1","User 2","User 3","User 4","Web Page" }
  1691.                    contact := GetNodeData(node);
  1692.                    str := WideQuoteStr(contact.title) + ',' +
  1693.                      WideQuoteStr(contact.name) + ',' +
  1694.                      WideQuoteStr(contact.surname) + ',' +
  1695.                      WideQuoteStr(contact.org) + ',' +
  1696.                      WideQuoteStr(contact.email) + ',' +
  1697.                      WideQuoteStr(contact.displayname) + ',' +
  1698.                      WideQuoteStr(contact.home) + ',' +
  1699.                      WideQuoteStr(contact.work) + ',' +
  1700.                      WideQuoteStr(contact.cell) + ',' +
  1701.                      WideQuoteStr(contact.fax) + ',' +
  1702.                      WideQuoteStr(contact.other) + ',' +
  1703.                      WideQuoteStr(GetContactDefPhone(contact));
  1704.                    sl.add(str);
  1705.                 end;
  1706.              except
  1707.              end;
  1708.              node := GetNext(node);
  1709.           until node = nil;
  1710.         end;
  1711.         sl.SaveToFile(FileName);
  1712.         sl.Free;
  1713.       end;
  1714.     3:begin//XML
  1715.         XML := TXML.Create();
  1716.         try
  1717.          XML.TagName := 'fma_contacts';
  1718.  
  1719.          with ListContacts do
  1720.          begin
  1721.           Node := GetFirst();
  1722.  
  1723.           while assigned(Node) do
  1724.           begin
  1725.            if Selected[Node] then
  1726.             with XML.AddChild('contact') do
  1727.             begin
  1728.              Contact := GetNodeData(Node);
  1729.  
  1730.              AddChild('title',   HTMLEncode(UTF8Encode(contact.title), False));
  1731.              AddChild('name',    HTMLEncode(UTF8Encode(contact.name), False));
  1732.              AddChild('surname', HTMLEncode(UTF8Encode(contact.surname), False));
  1733.              AddChild('org',     HTMLEncode(UTF8Encode(contact.org), False));
  1734.              AddChild('email',   HTMLEncode(UTF8Encode(contact.email), False));
  1735.              AddChild('home',    HTMLEncode(UTF8Encode(contact.home), False));
  1736.              AddChild('work',    HTMLEncode(UTF8Encode(contact.work), False));
  1737.              AddChild('cell',    HTMLEncode(UTF8Encode(contact.cell), False));
  1738.              AddChild('fax',     HTMLEncode(UTF8Encode(contact.fax), False));
  1739.              AddChild('other',   HTMLEncode(UTF8Encode(contact.other), False));
  1740.             end;
  1741.  
  1742.            Node := GetNext(Node);
  1743.           end;
  1744.          end;
  1745.  
  1746.          XML.Save(FileName);
  1747.  
  1748.         finally
  1749.          XML.Free();
  1750.         end;
  1751.  
  1752.       end;
  1753.     4:begin//HTML
  1754.         sl := TStringList.Create;
  1755.         sl.Add('<html><head><meta content="text/html;charset=utf-8" http-equiv="content-type">');
  1756.         sl.Add('<title>FMA Contacts</title></head><body>');
  1757.         sl.Add('<TABLE BORDER="1">');
  1758.         sl.Add('<TR><TD>Title</TD><TD>Name</TD><TD>Surname</TD><TD>Organization</TD><TD>Email</TD>');
  1759.         sl.Add('<TD>Home</TD><TD>Work</TD><TD>Cell</TD><TD>Fax</TD><TD>Other</TD></TR>');
  1760.         with ListContacts do begin
  1761.           node := GetFirst;
  1762.           repeat
  1763.              try
  1764.                 if Selected[node] then begin
  1765.                    contact := GetNodeData(node);
  1766.                    str := '<TR>';
  1767.                    str := str + '<TD>' + HTMLEncode(UTF8Encode(contact.title),False) + '</TD>';
  1768.                    str := str + '<TD>' + HTMLEncode(UTF8Encode(contact.name),False) + '</TD>';
  1769.                    str := str + '<TD>' + HTMLEncode(UTF8Encode(contact.surname),False) + '</TD>';
  1770.                    str := str + '<TD>' + HTMLEncode(UTF8Encode(contact.org),False) + '</TD>';
  1771.                    str := str + '<TD>' + HTMLEncode(UTF8Encode(contact.email),False) + '</TD>';
  1772.                    str := str + '<TD>' + HTMLEncode(UTF8Encode(contact.home),False) + '</TD>';
  1773.                    str := str + '<TD>' + HTMLEncode(UTF8Encode(contact.work),False) + '</TD>';
  1774.                    str := str + '<TD>' + HTMLEncode(UTF8Encode(contact.cell),False) + '</TD>';
  1775.                    str := str + '<TD>' + HTMLEncode(UTF8Encode(contact.fax),False) + '</TD>';
  1776.                    str := str + '<TD>' + HTMLEncode(UTF8Encode(contact.other),False) + '</TD>';
  1777.                    str := str + '</TR>';
  1778.                    sl.add(str);
  1779.                 end;
  1780.              except
  1781.              end;
  1782.              node := GetNext(node);
  1783.           until node = nil;
  1784.         end;
  1785.         sl.Add('</TABLE>');
  1786.         sl.Add('</body></html>');
  1787.         sl.SaveToFile(FileName);
  1788.         sl.Free;
  1789.       end;
  1790.   end;
  1791. end;
  1792.  
  1793. procedure TfrmSyncPhonebook.ForceNewContactClick(Sender: TObject);
  1794. begin
  1795.   ForceContact(0);
  1796. end;
  1797.  
  1798. procedure TfrmSyncPhonebook.ForceContact(State: integer);
  1799. var
  1800.   node: PVirtualNode;
  1801.   contact: PContactData;
  1802. begin
  1803.   with ListContacts do
  1804.   try
  1805.     BeginUpdate;
  1806.     node := GetFirst;
  1807.     repeat
  1808.       try
  1809.         if Selected[node] then begin
  1810.           contact := GetNodeData(node);
  1811.           if contact.StateIndex <> 0 then contact.StateIndex := State;
  1812.         end;
  1813.       except
  1814.       end;
  1815.       node := GetNext(node);
  1816.     until node = nil;
  1817.   finally
  1818.     EndUpdate;  
  1819.   end;
  1820. end;
  1821.  
  1822. procedure TfrmSyncPhonebook.ListContactsAfterPaint(
  1823.   Sender: TBaseVirtualTree; TargetCanvas: TCanvas);
  1824. begin
  1825.   NoItemsPanel.Visible := ListContacts.ChildCount[nil] = 0;
  1826. end;
  1827.  
  1828. procedure TfrmSyncPhonebook.PopupMenu1Popup(Sender: TObject);
  1829. var
  1830.   i: integer;
  1831.   m: TMenuItem;
  1832.   contact: PContactData;
  1833. begin
  1834.   DownloadEntirePhonebook1.Enabled := Form1.FConnected and not Form1.FObex.Connected;
  1835.   Properties1.Enabled := ListContacts.SelectedCount = 1;
  1836.   AddtoGroup1.Clear;
  1837.   if Assigned(Form1.FNodeGroups) then begin
  1838.     for i := 0 to Form1.FNodeGroups.Count-1 do begin
  1839.       m := TMenuItem.Create(nil);
  1840.       try
  1841.         m.AutoHotkeys := maManual;
  1842.         m.Caption := Form1.FNodeGroups.Item[i].Text;
  1843.         m.Tag := Form1.FNodeGroups.Item[i].StateIndex;
  1844.         m.ImageIndex := 53;
  1845.         m.OnClick := AddToGroupClick;
  1846.         AddtoGroup1.Add(m);
  1847.       except
  1848.         m.Free;
  1849.       end;
  1850.     end;
  1851.   end;
  1852.   if Properties1.Enabled then begin
  1853.     contact := ListContacts.GetNodeData(ListContacts.FocusedNode);
  1854.     AddtoGroup1.Enabled := (AddtoGroup1.Count <> 0) and // do we have groups at all?
  1855.       (contact.StateIndex in [1,3]); // work only for modified and normal contacts, exclude new and deleted ones
  1856.   end
  1857.   else
  1858.     AddtoGroup1.Enabled := False;
  1859. end;
  1860.  
  1861. procedure TfrmSyncPhonebook.AddToGroupClick(Sender: TObject);
  1862. var
  1863.   Node: PVirtualNode;
  1864.   Person: PContactData;
  1865.   i,index: integer;
  1866.   cname,cnumb: WideString;
  1867.   dlg: TfrmStatusDlg;
  1868. begin
  1869.   dlg := ShowStatusDlg('Adding to Group...');
  1870.   with dlg do
  1871.     try
  1872.       Node := ListContacts.GetFirst;
  1873.       while Node <> nil do
  1874.       try
  1875.         if ListContacts.Selected[Node] then begin
  1876.           Person := ListContacts.GetNodeData(Node);
  1877.           cname := GetContactFullName(Person);
  1878.           with TfrmAddToGroup.Create(nil) do
  1879.           try
  1880.             Contact := Person;
  1881.             lblGroup.Caption := (Sender as TMenuItem).Caption;
  1882.             if (clNumbers.Count = 1) or (ShowModal = mrOk) then begin
  1883.               { Default number }
  1884.               if RadioButton1.Checked then begin
  1885.                 index := ContactDefPosition(Person);
  1886.                 if index < 1 then
  1887.                   index := Form1.LocatePBIndex('ME',cname,GetContactDefPhone(Person));
  1888.                 if index > 0 then begin
  1889.                   { Remember found position, ie. make a cache here }
  1890.                   ContactDefPosition(Person,index);
  1891.                   { Add to group }
  1892.                   Form1.Status('Adding to group...');
  1893.                   with (Sender as TMenuItem) do begin
  1894.                     Form1.TxAndWait('AT*ESAG='+IntToStr(Tag)+',2,'+IntToStr(index));
  1895.                     // TODO: Do not add dublicates to groups
  1896.                     //Form1.ExplorerAddToGroup(Tag,cname);
  1897.                   end;
  1898.                 end;
  1899.               end
  1900.               else begin
  1901.                 { Custom numbers }
  1902.                 for i := 0 to clNumbers.Count-1 do
  1903.                   if clNumbers.Checked[i] then begin
  1904.                     cnumb := GetNumber(i);
  1905.                     index := GetContactPosition(Person,cnumb);
  1906.                     if index < 1 then
  1907.                       index := Form1.LocatePBIndex('ME',cname,cnumb);
  1908.                     if index > 0 then begin
  1909.                       { Remember found position, ie. make a cache here }
  1910.                       SetContactPosition(Person,cnumb,index);
  1911.                       { Add to group }
  1912.                       Form1.Status('Adding to group...');
  1913.                       with (Sender as TMenuItem) do begin
  1914.                         Form1.TxAndWait('AT*ESAG='+IntToStr(Tag)+',2,'+IntToStr(index));
  1915.                         //Form1.ExplorerAddToGroup(Tag,cname);
  1916.                       end;
  1917.                     end;
  1918.                   end;
  1919.               end;
  1920.             end
  1921.             else
  1922.               dlg.Close;
  1923.           finally
  1924.             Free;
  1925.           end;
  1926.         end;
  1927.       finally
  1928.         Node := ListContacts.GetNext(Node);
  1929.       end;
  1930.       Form1.InitGroups;
  1931.       Form1.SaveData;
  1932.     finally
  1933.       Free;
  1934.     end;
  1935. end;
  1936.  
  1937. function TfrmSyncPhonebook.UpdatePositions: boolean;
  1938. var
  1939.   contact: PContactData;
  1940. //  Node :PVirtualNode;
  1941. //  NeedUpdate: Boolean;
  1942.   sl: TStringList;
  1943.   function SameContact(Name,Number: WideString): boolean;
  1944.   var
  1945.     s: string;
  1946.   begin
  1947.     s := contact.name;
  1948.     if contact.surname <> '' then s := s + ' ' + contact.surname;
  1949.     Result := (s = Name) and
  1950.      ((contact.home = Number) or
  1951.       (contact.work = Number) or
  1952.       (contact.cell = Number) or
  1953.       (contact.fax = Number) or
  1954.       (contact.other = Number)
  1955.      );
  1956.   end;
  1957.   function FindNumber: integer;
  1958.   var
  1959.     i,position: integer;
  1960.     slTmp: TStringList;
  1961.     Name, Number: String;
  1962.   begin
  1963.     Result := 0;
  1964.     slTmp := TStringList.Create;
  1965.     try
  1966.       for i := 0 to sl.Count-1 do
  1967.         if pos('+CPBR', sl[i]) = 1 then begin
  1968.           slTmp.DelimitedText := sl[i];
  1969.  
  1970.           position := StrToInt(slTmp.Strings[1]);
  1971.           Number := slTmp.Strings[2];
  1972.           if Form1.FUseUTF8 then
  1973.             Name := UTF8Decode(slTmp.Strings[4])
  1974.           else
  1975.             Name := slTmp.Strings[4];
  1976.           if (Length(Name) > 2) and (Name[Length(Name)-1] = '/') then
  1977.             SetLength(Name,Length(Name)-2);  
  1978.  
  1979.           if (slTmp.Strings[3] = '145') and (Number[1] <> '+') then
  1980.             Number := '+' + Number;
  1981.  
  1982.           if SameContact(Name,Number) then begin
  1983.             Result := position;
  1984.             Form1.Debug(Name+' '+Number+' @ '+IntToStr(position));
  1985.             break;
  1986.           end;
  1987.         end;
  1988.     finally
  1989.       slTmp.Free;
  1990.     end;
  1991.   end;
  1992. begin
  1993.   Result := False;
  1994.   // temporary exit, since we are using another method to retrieve PB position. 
  1995.   exit;
  1996.   {
  1997.   if Form1.FConnected then begin
  1998.     if (cardinal(Form1.FNodeContactsME.Count) = ListContacts.ChildCount[nil]) then begin
  1999.       NeedUpdate := False;
  2000.       Node := ListContacts.GetFirst;
  2001.       while Node <> nil do
  2002.       try
  2003.         contact := ListContacts.GetNodeData(Node);
  2004.         if NumPosEmpty(contact) then begin // if some item doesn't have position yet?
  2005.           NeedUpdate := true;
  2006.           break;
  2007.         end;
  2008.       finally
  2009.         Node := ListContacts.GetNext(Node);
  2010.       end;
  2011.     end
  2012.     else begin
  2013.       NeedUpdate := True;
  2014.       Result := True; // we'll add/remove items, so always report 'change'
  2015.     end;
  2016.     if NeedUpdate then begin
  2017.       sl := TStringList.Create;
  2018.       try
  2019.         Form1.GetPhonebook('ME',sl);
  2020.         // TODO: da se slohi cikyl po sl, a gore po ListContacts
  2021.         Node := ListContacts.GetFirst;
  2022.         while Node <> nil do
  2023.         try
  2024.           contact := ListContacts.GetNodeData(Node);
  2025.           if NumPosEmpty(contact) then begin
  2026.             // TODO: Add phone type, not only cell
  2027.             contact.Position.cell := FindNumber;
  2028.             Result := True;
  2029.           end;
  2030.         finally
  2031.           Node := ListContacts.GetNext(Node);
  2032.         end;
  2033.       finally
  2034.         sl.Free;
  2035.       end;
  2036.     end;
  2037.   end;
  2038.   }
  2039. end;
  2040.  
  2041. function TfrmSyncPhonebook.DoEdit(AsNew: boolean; NewNumber: string; ContactData: PContactData): boolean;
  2042. var
  2043.   Node: PVirtualNode;
  2044.   procedure SyncChanges;
  2045.   begin
  2046.     ListContacts.Sort(nil, ListContacts.Header.SortColumn, ListContacts.Header.SortDirection);
  2047.     ListContacts.Update;
  2048.     Form1.UpdateMEPhonebook;
  2049.     { Focus moved edited item in the list }
  2050.     if ListContacts.FocusedNode <> nil then begin
  2051.       Node := ListContacts.FocusedNode;
  2052.       ListContacts.FocusedNode := nil;
  2053.       ListContacts.FocusedNode := Node;
  2054.     end;
  2055.   end;
  2056. begin
  2057.   Result := False;
  2058.   if ContactData <> nil then begin
  2059.     SelContact := ContactData;
  2060.     AsNew := False;
  2061.   end;
  2062.   if AsNew then State := 0
  2063.   else begin
  2064.     State := Selcontact.StateIndex;
  2065.     if State = 0 then State := 4 //new >> modified
  2066.       else if State = 3 then State := 1;
  2067.   end;
  2068.   with TfrmEditContact.Create(nil) do
  2069.   try
  2070.     IsNew := (State = 0) or (Selcontact = nil);
  2071.     // set restrictions
  2072.     MaxFullNameLen := FMaxNameLen;
  2073.     txtName.MaxLength := FMaxNameLen;
  2074.     txtDisplayAs.MaxLength := FMaxNameLen; //??
  2075.     txtTitle.MaxLength := FMaxTitleLen;
  2076.     txtOrganization.MaxLength := FMaxOrgLen;
  2077.     txtEmail.MaxLength := FMaxMailLen;
  2078.     txtHome.MaxLength := FMaxTellen;
  2079.     txtWork.MaxLength := FMaxTellen;
  2080.     txtCell.MaxLength := FMaxTellen;
  2081.     txtFax.MaxLength := FMaxTellen;
  2082.     txtOther.MaxLength := FMaxTellen;
  2083.     // update contact state
  2084.     if IsNew then begin
  2085.       FillChar(contact,SizeOf(contact),0);
  2086.       contact.cell := NewNumber;
  2087.     end
  2088.     else
  2089.       contact := Selcontact^;
  2090.     // record undo info, or set own card mode  
  2091.     if ContactData = nil then FUndoEdit := contact
  2092.       else UseOwnMode := True;
  2093.     // edit contact  
  2094.     if ShowModal = mrOk then begin
  2095.       if Modified then with ListContacts do begin
  2096.         // apply total updates
  2097.         BeginUpdate;
  2098.         try
  2099.           if IsNew then begin // create new node
  2100.             FocusedNode := AddChild(nil);
  2101.             Selcontact := ListContacts.GetNodeData(FocusedNode);
  2102.           end;
  2103.           { copy all data }
  2104.           Selcontact^ := contact;
  2105.           if IsNew then begin // new node, update IDs
  2106.             Selcontact^.LUID := '';
  2107.             Selcontact^.CDID := NewGUID;
  2108.           end;
  2109.           Selcontact^.stateindex := State;
  2110.           if State = 4 then Selcontact^.stateindex := 0;
  2111.           if (State > 0) and (ContactData = nil) then
  2112.             UndoLastChange1.Enabled := True; // undo not works on new contact
  2113.           Result := True;
  2114.         finally
  2115.           RenderGUIDs;
  2116.           EndUpdate;
  2117.           SyncChanges;
  2118.         end;
  2119.       end else
  2120.       if customModified then with ListContacts do begin
  2121.         // apply only Fma custom data updates
  2122.         BeginUpdate;
  2123.         try
  2124.           { copy custom data only }
  2125.           Selcontact^.displayname := contact.displayname;
  2126.           Selcontact^.DefaultIndex := contact.DefaultIndex;
  2127.           Selcontact^.Position := contact.Position;
  2128.           Selcontact^.picture := contact.picture;
  2129.           Selcontact^.sound := contact.sound;
  2130.           SelContact^.CDID := contact.CDID;
  2131.           if ContactData = nil then UndoLastChange1.Enabled := True;
  2132.           Result := True;
  2133.         finally
  2134.           EndUpdate;
  2135.           SyncChanges;
  2136.         end;
  2137.       end;
  2138.     end;
  2139.   finally
  2140.     Free;
  2141.   end;
  2142. end;
  2143.  
  2144. procedure TfrmSyncPhonebook.UndoLastChange1Click(Sender: TObject);
  2145. var
  2146.   Node :PVirtualNode;
  2147. begin
  2148.   Node := ListContacts.GetFirst;
  2149.   while Node <> nil do begin
  2150.     if Node.Index = FUndoIndx then break;
  2151.     Node := ListContacts.GetNext(Node);
  2152.   end;
  2153.   if Node <> nil then begin
  2154.     { multiselect is enabled, so this is not correct
  2155.     if ListContacts.FocusedNode <> nil then
  2156.       ListContacts.FocusedNode.States := ListContacts.FocusedNode.States - [vsSelected];
  2157.     Node.States := Node.States + [vsSelected];
  2158.     }
  2159.     ListContacts.FocusedNode := Node;
  2160.     ListContacts.ScrollIntoView(Node,True);
  2161.     Selcontact := ListContacts.GetNodeData(Node);
  2162.     SelContact^ := FUndoEdit;
  2163.     UndoLastChange1.Enabled := False;
  2164.     ListContacts.Repaint;
  2165.     { refresh explorer view on-the-fly (it's quick, don't worry) }
  2166.     Form1.UpdateMEPhonebook;
  2167.   end;  
  2168. end;
  2169.  
  2170. procedure TfrmSyncPhonebook.ListContactsIncrementalSearch(
  2171.   Sender: TBaseVirtualTree; Node: PVirtualNode;
  2172.   const SearchText: WideString; var Result: Integer);
  2173. var
  2174.   Contact: PContactData;
  2175.   Text: WideString;
  2176. begin
  2177.   Contact := ListContacts.GetNodeData(Node);
  2178.   Text := Copy(GetContactDisplayName(Contact),1,Length(SearchText));
  2179.   Result := WideCompareText(SearchText,Text);
  2180. end;
  2181.  
  2182. procedure TfrmSyncPhonebook.ListContactsKeyDown(Sender: TObject;
  2183.   var Key: Word; Shift: TShiftState);
  2184. begin
  2185.   if (Key = VK_RETURN) and (ListContacts.SelectedCount = 1) then
  2186.     btnEditClick(nil); 
  2187. end;
  2188.  
  2189. procedure TfrmSyncPhonebook.ImportContacts1Click(Sender: TObject);
  2190. var
  2191.   i,adds,mods: integer;
  2192.   Node,ANode: PVirtualNode;
  2193.   contact: PContactData;
  2194.   sl: TStringList;
  2195.   F: WideString;
  2196.   Modified: boolean;
  2197.   dlg: TfrmConnect;
  2198. begin
  2199.   if not OpenDialog1.Execute then exit;
  2200.   Update;
  2201.   dlg := GetProgressDialog;
  2202.   try
  2203.     if Form1.CanShowProgress then
  2204.       dlg.ShowProgress(Form1.FProgressLongOnly);
  2205.     dlg.Initialize(OpenDialog1.Files.Count,'Importing phonebook contacts');
  2206.  
  2207.     Form1.Status('Importing contacts...');
  2208.     SyncLog('Import started');
  2209.  
  2210.     adds := 0; mods := 0;
  2211.     //ListContacts.BeginUpdate;
  2212.     sl := TStringList.Create;
  2213.     try
  2214.       for i := 0 to OpenDialog1.Files.Count-1 do begin
  2215.         sl.LoadFromFile(OpenDialog1.Files[i]);
  2216.         dlg.IncProgress(1);
  2217.         VCard.Clear;
  2218.         VCard.Raw := sl;
  2219.         F := GetvCardFullName(VCard);
  2220.         Modified := False;
  2221.         //erase the old entry if present
  2222.         if FindContact(F,ANode) then begin
  2223.           contact := ListContacts.GetNodeData(ANode);
  2224.           case MessageDlg(F + ' [' + GetContactDefPhone(contact) +
  2225.             '] already exists. Do you want to replace it now?'#13#13'(Click No to add it as a New contact)',
  2226.             mtConfirmation,[mbYes,mbNo,mbCancel],0) Of
  2227.             mrYes: begin
  2228.               ListContacts.DeleteNode(ANode);
  2229.               SyncLog(F + ' modified in FMA by Import.');
  2230.               Modified := True;
  2231.             end;
  2232.             mrNo: SyncLog(F + ' added to FMA by Import (as dublicate).');
  2233.             mrCancel: Abort;
  2234.           end;
  2235.         end
  2236.         else SyncLog(F + ' added to FMA by Import.');
  2237.         Node := ListContacts.AddChild(nil);
  2238.         contact := ListContacts.GetNodeData(Node);
  2239.         vCard2Contact(VCard,contact);
  2240.         if Modified then begin
  2241.           contact.stateindex := 1;
  2242.           inc(mods);
  2243.         end
  2244.         else begin
  2245.           contact.stateindex := 0;
  2246.           inc(adds);
  2247.         end;
  2248.         // TODO: add picture and sound support here....
  2249.         ListContacts.Update;
  2250.       end;
  2251.     finally
  2252.       sl.free;
  2253.       if (adds <> 0) or (mods <> 0) then begin
  2254.         RenderGUIDs;
  2255.         //ListContacts.EndUpdate;
  2256.         ListContacts.Sort(nil, ListContacts.Header.SortColumn, ListContacts.Header.SortDirection);
  2257.         ListContacts.Update;
  2258.         Form1.UpdateMEPhonebook;
  2259.         Form1.Debug('Imported '+IntToStr(adds+mods)+' item(s)... ('+IntToStr(adds)+' added, '+IntToStr(mods)+' modified)');
  2260.       end;
  2261.     end;
  2262.   finally
  2263.     FreeProgressDialog;
  2264.     SyncLog('Import finished');
  2265.     Form1.Status('Import complete.');
  2266.   end;
  2267. end;
  2268.  
  2269. function TfrmSyncPhonebook.FindContact(FullName: WideString;
  2270.   var AContact: PContactData): boolean;
  2271. var
  2272.   Node :PVirtualNode;
  2273. begin
  2274.   Result := False;
  2275.   Node := ListContacts.GetFirst;
  2276.   while Node <> nil do begin
  2277.     AContact := ListContacts.GetNodeData(Node);
  2278.     if WideCompareText(FullName,GetContactFullName(AContact)) = 0 then begin
  2279.       Result := True;
  2280.       break;
  2281.     end;
  2282.     Node := ListContacts.GetNext(Node);
  2283.   end;
  2284. end;
  2285.  
  2286. function TfrmSyncPhonebook.FindContact(FullName: WideString;
  2287.   var ANode: PVirtualNode): boolean;
  2288. var
  2289.   AContact: PContactData;  
  2290. begin
  2291.   Result := False;
  2292.   ANode := ListContacts.GetFirst;
  2293.   while ANode <> nil do begin
  2294.     AContact := ListContacts.GetNodeData(ANode);
  2295.     if WideCompareText(FullName,GetContactFullName(AContact)) = 0 then begin
  2296.       Result := True;
  2297.       break;
  2298.     end;
  2299.     ANode := ListContacts.GetNext(ANode);
  2300.   end;
  2301. end;
  2302.  
  2303. procedure TfrmSyncPhonebook.ClearChangedFlag1Click(Sender: TObject);
  2304. begin
  2305.   ForceContact(3);
  2306. end;
  2307.  
  2308. function TfrmSyncPhonebook.GetPhoneCapacity: Integer;
  2309. var
  2310.   i: Integer;
  2311.   buffer, stop: String;
  2312.   slTmp: TStrings;
  2313. begin
  2314.   Form1.TxAndWait('AT+CPBS="ME"');
  2315.   Form1.TxAndWait('AT+CPBR=?');
  2316.   // defaults
  2317.   buffer := '';
  2318.   stop := '510'; FMaxNameLen := 180; FMaxTelLen := 80;
  2319.   // +CPBR: (1-200),80,180
  2320.   for i := 0 to Form1.FRxBuffer.Count-1 do
  2321.     if Pos('+CPBR',Form1.FRxBuffer.Strings[i]) = 1 then begin
  2322.       buffer := Form1.FRxBuffer.Strings[i];
  2323.       break;
  2324.     end;
  2325.   for i := 1 to length(buffer) do begin
  2326.     if IsDelimiter('()-,', buffer, i) then buffer[i] := ' ';
  2327.   end;
  2328.   // +CPBR:  1 200  80 180
  2329.   if buffer <> '' then begin
  2330.     slTmp := TStringList.Create;
  2331.     try
  2332.       slTmp.DelimitedText := buffer;
  2333.       stop := slTmp.Strings[2];
  2334.       Form1.Debug('Phonebook: max entries = '+stop);
  2335.       FMaxTelLen := StrToInt(slTmp.Strings[3]);
  2336.       Form1.Debug('Phonebook: max tel length = '+slTmp.Strings[3]);
  2337.       FMaxNameLen := StrToInt(slTmp.Strings[4]);
  2338.       Form1.Debug('Phonebook: max name length = '+slTmp.Strings[4]);
  2339.     finally
  2340.       slTmp.Free;
  2341.     end;
  2342.   end;  
  2343.   Result := StrToInt(stop);
  2344. end;
  2345.  
  2346. procedure TfrmSyncPhonebook.OnConnected;
  2347. begin
  2348.   FMaxRecME := GetPhoneCapacity;
  2349. end;
  2350.  
  2351. procedure TfrmSyncPhonebook.RenderGUIDs;
  2352. var
  2353.   contact: PContactData;
  2354.   Node: PVirtualNode;
  2355. begin
  2356.   { Make sure all contacts' GUIDs are unique }
  2357.   Node := ListContacts.GetFirst;
  2358.   while Node <> nil do begin
  2359.     contact := ListContacts.GetNodeData(Node);
  2360.     repeat
  2361.       if IsUniqueGUID(contact) then break;
  2362.       contact.CDID := NewGUID;
  2363.     until False;
  2364.     Node := ListContacts.GetNext(Node);
  2365.   end;
  2366. end;
  2367.  
  2368. function TfrmSyncPhonebook.IsUniqueGUID(who: PContactData): boolean;
  2369. var
  2370.   Node: PVirtualNode;
  2371.   contact: PContactData;
  2372. begin
  2373.   { Checks whether who contact has an unique GUID field }
  2374.   Result := True;
  2375.   Node := ListContacts.GetFirst;
  2376.   while Node <> nil do begin
  2377.     contact := ListContacts.GetNodeData(Node);
  2378.     if (contact <> who) and (GUIDToString(contact.CDID) = GUIDToString(who.CDID)) then begin
  2379.       Result := False;
  2380.       break;
  2381.     end;
  2382.     Node := ListContacts.GetNext(Node);
  2383.   end;
  2384. end;
  2385.  
  2386. function TfrmSyncPhonebook.FullRefresh: boolean;
  2387. var
  2388.   sl : TStringList;
  2389.   cardstr : TStringList;
  2390.   //ListCont :TStringList;
  2391.   //str : String;
  2392.   slCC :TStringList;
  2393.   i:Integer;
  2394.   AsNew,isAgent,isBody: Boolean;
  2395.   migrate: TContactData;
  2396.   contact: PContactData;
  2397.   Node,TmpNode: PVirtualNode;
  2398. begin
  2399.   Result := False;
  2400.   //check if start OBEX
  2401.   if not Form1.FConnected then begin
  2402.      ShowMessage('The Sync Phonebook can''t start...try to restart your phone.');
  2403.      SyncLog('The Sync Phonebook can''t start...try to restart your phone.');
  2404.      btnSync.Enabled := True;
  2405.      Form1.ActionSyncPhonebook.Enabled := True;
  2406.      exit;
  2407.   end;
  2408.   Update;
  2409.   sl := TStringList.Create;
  2410.   cardstr := TStringList.Create;
  2411.   //ListCont := TStringList.Create;
  2412.   //Start get of entire phonebook
  2413.   Form1.ObexConnect('IRMC-SYNC');       //start sync process
  2414.   try
  2415.     Form1.ObexGetObject('telecom/pb.vcf',sl,True,'entire phonebook');
  2416.     slCC := TStringList.Create;
  2417.     try
  2418.       Form1.ObexGetObject('telecom/pb/luid/cc.log',slCC);   //Take CC
  2419.       CC := slCC.Strings[0];
  2420.     finally
  2421.       slCC.Free;
  2422.     end;
  2423.   finally
  2424.     Form1.ObexDisconnect;        //close the connection
  2425.   end;
  2426.   
  2427.   ListContacts.BeginUpdate;
  2428.   try
  2429.     { Mark all entries as deleted }
  2430.     Node := ListContacts.GetFirst;
  2431.     while Node <> nil do begin
  2432.       contact := ListContacts.GetNodeData(Node);
  2433.       contact.StateIndex := 2;
  2434.       Node := ListContacts.GetNext(Node);
  2435.     end;
  2436.     try
  2437.       { Process phonebook entries }
  2438.       isBody := False;
  2439.       VCard.clear;
  2440.       isAgent := False;
  2441.       for i := 0 to sl.Count - 1 do begin
  2442.         { check for nested vCard and ignore it, if any }
  2443.         if pos('AGENT', sl.Strings[i]) = 1 then isAgent := True;
  2444.         if isAgent then begin
  2445.           if pos('END', sl.Strings[i]) = 1 then isAgent := False;
  2446.           Continue;
  2447.         end;
  2448.         { process vCard data }
  2449.         if pos('BEGIN', sl.Strings[i]) = 1 then isBody := True;
  2450.         if isBody then begin
  2451.           cardstr.add(sl.Strings[i]);
  2452.         end;
  2453.         if pos('END', sl.Strings[i]) = 1 then begin
  2454.           isBody := False;
  2455.           VCard.Raw := cardstr;
  2456.           cardstr.Clear;
  2457.           if LFindContact(VCard.LUID,contact) then begin
  2458.             migrate := contact^;
  2459.             AsNew := False;
  2460.           end
  2461.           else
  2462.             AsNew := True;
  2463.           if not AsNew then
  2464.             EraseContact(VCard.LUID,False);
  2465.           //Add new Node and Parse VCard
  2466.           Node := ListContacts.AddChild(nil);
  2467.           contact := ListContacts.GetNodeData(Node);
  2468.           vCard2Contact(VCard,contact);
  2469.           contact.stateindex := 3;
  2470.           //Migrate Fma internal settings
  2471.           if not AsNew then
  2472.             MigrateContact(@migrate,contact);
  2473.           // TODO: add picture and sound support here....
  2474.           SyncLog(GetvCardFullName(VCard) + ' added to FMA by phone.');
  2475.           VCard.Clear;
  2476.         end;
  2477.       end;
  2478.     finally
  2479.       { Clear all deleted entries }
  2480.       Node := ListContacts.GetFirst;
  2481.       while Node <> nil do begin
  2482.         contact := ListContacts.GetNodeData(Node);
  2483.         if contact.StateIndex <> 3 then begin
  2484.           TmpNode := Node;
  2485.           Node := ListContacts.GetNext(Node);
  2486.           ListContacts.DeleteNode(TmpNode);
  2487.           SyncLog(GetContactFullName(contact) + ' is obsolete in FMA.');
  2488.         end
  2489.         else
  2490.           Node := ListContacts.GetNext(Node);
  2491.       end;
  2492.     end;
  2493.     UpdatePositions;
  2494.     DoFirstImportCheck;
  2495.     Result := True;
  2496.   finally
  2497.     ListContacts.EndUpdate;
  2498.     ListContacts.Sort(nil, ListContacts.Header.SortColumn, ListContacts.Header.SortDirection);
  2499.     ListContacts.Update;
  2500.     UndoLastChange1.Enabled := False;
  2501.     sl.Free;
  2502.     cardstr.Free;
  2503.     //listcont.Free;
  2504.   end;
  2505. end;
  2506.  
  2507. function TfrmSyncPhonebook.LFindContact(LUID: Widestring;
  2508.   var AContact: PContactData): Boolean;
  2509. var
  2510.   Node: PVirtualNode;
  2511.   contact: PContactData;
  2512. begin
  2513.   Result := False;
  2514.   with ListContacts do begin
  2515.     Node := GetFirst;
  2516.     while Node <> nil do begin
  2517.       contact := GetNodeData(node);
  2518.       if LUID = contact.LUID then begin
  2519.          AContact := contact;
  2520.          Result := True;
  2521.          break;
  2522.       end;
  2523.       Node := GetNext(Node);
  2524.     end;
  2525.   end;
  2526. end;
  2527.  
  2528. procedure TfrmSyncPhonebook.DoFirstImportCheck;
  2529. var
  2530.   Node: PVirtualNode;
  2531.   contact: PContactData;
  2532.   HasCells,HasHomes: boolean;
  2533.   s: string;
  2534. begin
  2535.   HasCells := False;
  2536.   HasHomes := False;
  2537.   with ListContacts do begin
  2538.     Node := GetFirst;
  2539.     while Node <> nil do begin
  2540.       contact := GetNodeData(node);
  2541.       if contact.cell <> '' then HasCells := True;
  2542.       if contact.home <> '' then HasHomes := True;
  2543.       Node := GetNext(Node);
  2544.     end;
  2545.   end;
  2546.   { Is this first import from 'old' phone which keep numbers into Home position
  2547.     instead of Cell one? This usualy happens when one imports all contacts
  2548.     from its SIM card into Phone's memory (phonebook). }
  2549.   if not HasCells and HasHomes then
  2550.     { Yes, offer exchange }
  2551.     if MessageDlg('It seams that all your phone numbers are stored into Home positions. '+
  2552.       'Do you wish to exchange them with Cell ones?',mtConfirmation,[mbYes,mbNo],0) = ID_YES then begin
  2553.       with ListContacts do begin
  2554.         Node := GetFirst;
  2555.         while Node <> nil do begin
  2556.           contact := GetNodeData(node);
  2557.           if contact.home <> '' then begin
  2558.             s := contact.cell;
  2559.             contact.cell := contact.home;
  2560.             contact.home := s;
  2561.             contact.StateIndex := 1; // modified
  2562.           end;
  2563.           Node := GetNext(Node);
  2564.         end;
  2565.       end;
  2566.     end;
  2567. end;
  2568.  
  2569. procedure TfrmSyncPhonebook.FirstLast1Click(Sender: TObject);
  2570. begin
  2571.   (Sender as TMenuItem).Checked := True;
  2572.   ListContacts.Sort(nil, ListContacts.Header.SortColumn, ListContacts.Header.SortDirection);
  2573. end;
  2574.  
  2575. procedure TfrmSyncPhonebook.DownloadEntirePhonebook1Click(Sender: TObject);
  2576. begin
  2577.   if MessageDlg('Local Phonebook will be replaced with a fresh copy from the phone.'#13#13+
  2578.     'Any local changes will be lost. Do you wish to continue?',
  2579.     mtConfirmation,[mbYes,mbNo],0) = ID_YES then begin
  2580.       ListContacts.Clear;
  2581.       FullRefresh;
  2582.     end;
  2583. end;
  2584.  
  2585. function TfrmSyncPhonebook.FindContact(Number: WideString): WideString;
  2586. var
  2587.   Node :PVirtualNode;
  2588.   contact: PContactData;
  2589. begin
  2590.   Result := '';
  2591.   Node := ListContacts.GetFirst;
  2592.   while Node <> nil do begin
  2593.     contact := ListContacts.GetNodeData(Node);
  2594.     if IsContactPhone(contact,Number) then begin
  2595.       Result := GetContactFullName(contact);
  2596.       break;
  2597.     end;
  2598.     Node := ListContacts.GetNext(Node);
  2599.   end;
  2600. end;
  2601.  
  2602. procedure TfrmSyncPhonebook.FormStorage1SavePlacement(Sender: TObject);
  2603. var
  2604.   s: string;
  2605.   i: integer;
  2606. begin
  2607.   with ListContacts.Header do begin
  2608.     s := IntToStr(SortColumn)+','+IntToStr(Ord(SortDirection));
  2609.     for i := 0 to Columns.Count-1 do
  2610.       s := s+','+IntToStr(Columns[i].Width)+','+IntToStr(Columns[i].Position);
  2611.   end;
  2612.   FormStorage1.StoredValue['ListHeader'] := s;
  2613. end;
  2614.  
  2615. procedure TfrmSyncPhonebook.FormStorage1RestorePlacement(Sender: TObject);
  2616. var
  2617.   s: widestring;
  2618.   i: integer;
  2619. begin
  2620.   s := FormStorage1.StoredValue['ListHeader'];
  2621.   if s <> '' then
  2622.     try
  2623.       with ListContacts.Header do begin
  2624.         SortColumn := StrToInt(GetFirstToken(s));
  2625.         SortDirection := TSortDirection(StrToInt(GetFirstToken(s)));
  2626.         for i := 0 to Columns.Count-1 do begin
  2627.           Columns[i].Width := StrToInt(GetFirstToken(s));
  2628.           Columns[i].Position := StrToInt(GetFirstToken(s));
  2629.         end;
  2630.       end;
  2631.       if FirstLast1.Checked then FirstLast1Click(FirstLast1)
  2632.         else FirstLast1Click(LastFirst1);
  2633.     except
  2634.     end;
  2635. end;
  2636.  
  2637. procedure TfrmSyncPhonebook.ListContactsHeaderMouseUp(Sender: TVTHeader;
  2638.   Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
  2639. begin
  2640.   FormStorage1SavePlacement(nil);
  2641. end;
  2642.  
  2643. end.
  2644.  
  2645.